깃헙 유저네임을 입력 후 버튼을 누르면, retrofit 을 통해 api 를 호출하여 유저 정보를 불러오는 예제를 구현해보았다.
싱글톤 패턴
싱글톤 패턴 관련 설명 및 코드는 해당 블로그의 내용을 인용했다.
object, compainon object 키워드 선언이 있다. class MyInfoManager private constructor() {
// singleton pattern
companion object {
private var instance: MyInfoManager? = null
fun getInstance(): MyInfoManager {
return instance ?: synchronized(this) {
instance ?: MyInfoManager().also {
instance = it
}
}
}
}
private var name = ""
...
fun setInfo(name: Strng) {
self.name = name
}
fun getName(): String {
return name
}
}
...
var myInfo1 = MyInfoManager.getInstance()
var myInfo2 = MyInfoManager.getInstance()
var myInfo3 = MyInfoManager.getInstance()
companion 변수 instance를 생성.textField 에 깃헙 유저네임을 입력 후 버튼을 누르면, userName 과 userInfo를 불러오는 api 를 호출하는 코드를 구현해보았다.
class Person private constructor () { ... }
private 접근자를 통해 외부에서 생성자를 통한 객체생성을 막는다.
private constructor 키워드를 사용하면 클래스 내부에서만 생성자에 접근 할 수 있다. 코틀린에서 singleton을 만들때 생성자를 외부에 노출하지 않는 방법이다.
생성자
클래스 생성 시 같이 선언하는게 주생성자이며 1개만 선언 가능하다. 주생성자에 실행문은 넣을 수 없기 때문에 초기화가 필요하다면 init 블럭 내에 적으면 된다. 부생성자(secondary constructor) 는 여러개 가질 수 있고, constructor 키워드를 생략할 수 없다.
(생략)
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
너무 당연한 말이지만... 네트워크 통신이기 때문에 인터넷 사용권한 추가, 네트워크 트래픽 사용을 허용해야 한다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 1) 인터넷 사용권한 -->
<uses-permission android:name="android.permission.INTERNET" />
<application
<!-- 2) 네트워크 트래픽 사용 허용 -->
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Retropit"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
유저 이름을 입력 받을 TextField, 불러오기 버튼, 유저 정보를 나타낼 TextView 를 선언했다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<EditText
android:id="@+id/usernameInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter GitHub Username" />
<TextView
android:id="@+id/userName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="user info"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:padding="10dp" />
<TextView
android:id="@+id/userInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="user info"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:padding="10dp" />
<Button
android:id="@+id/mainButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="불러오기" />
</LinearLayout>
통신으로 받아온 데이터를 담을 클래스를 생성해준다.
// User.kt
package com.example.retropit
// 데이터 클래스 정의
data class User (
val login: String,
val id: Int,
val avatar_url: String
)
// Api.kt
package com.example.retropit
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path
// Retrofit에게 네트워크 요청의 구성을 설명하는 인터페이스
// 이 인터페이스를 기반으로 Retrofit이 요청을 수행하는 실제 객체를 생성.
interface Api {
@GET("users/{username}")
fun getUser(@Path("username") username: String) : Call<User>
}
Retrofit이 네트워트 요청을 수행하는데에 필요한 정보들을 포함하고 있다. 중요한것은 어노테이션, 매개변수, 반환 타입이다. Get, Path 어노테이션에 대한 설명은 다음과 같다
@GET
: HTTP GET 요청을 의미
사용예시
@GET("users/{username}")
https://api.github.com/users/{username}와 같은 URL에 GET 요청을 보내겠다는 의미. 여기서 username 은 매개변수로, 요청 시 동적으로 채워지는 부분이다. 만약 https://api.github.com/users/someUsername로 요청을 보내는 경우, "someUsername"으로 {username}을 채우게 된다.
@Path
: 경로에서 동적으로 값을 넣는 부분을 표시함
사용예시
@Path(”username”)
username이라는 경로 매개변수에 함수 호출 시 전달하는 username 값을 삽입하겠다는 의미이다. 만약 getUser(”JohnDoe”)를 호출하면 https://api.github.com/users/JohnDoe 로 요청을 보낸다.
Call
: Call< User>는 비동기적으로User데이터를 받을 수 있는 객체를 의미함
Call 객체는 Retrofit 의 요청을 enqueue() 로 비동기 실행하거나, execute() 로 동기 실행 할 수 있다.
아래 코드에서 getUser라는 함수가 Call<User>를 반환하도록 정의하고 있다. (User 는 위에서 만들어줬던 데이터 클래스)
class MainActivity : AppCompatActivity() {
lateinit var activityMainBinding: ActivityMainBinding
lateinit var api: Api
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// viewBinding 초기화
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
// retrofit 초기화
val retrofit = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
// Retrofit 객체에게 인터페이스에 정의된 대로 네트워크 요청을 수행하는 구현 객체를 만들라고 요청
api = retrofit.create(api::class.java)
enableEdgeToEdge()
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
}
}