728x90
Goni95_App
사용 기술
- 1. Kotlin Extension Functions
- 2. retrofit
- 3. OkHttp HttpLoggingInterceptor, Interceptor
- 4. High Order Function
특징
git과 안드로이드 프로젝트 연결
- GitBash 처음 사용할 경우 : git config --global user.name "Your Name Here"
git config --global user.email "your_email@youremail.com"
- Repository를 생성 : mkdir /MyProject
- 디렉토리로 이동 : cd ~/MyProject (로컬 컴퓨터의 최상위 단계 디렉토리, Users 폴더를 의미)
- 로컬 저장소와 깃허브 원격 저장소 연결 : git remote add origin 깃 주소
- 리모트 저장소 연결조회 : git remote와 git remote -v
- Readme.md 파일 생성 : touch Readme.md
- Readme.md 글 작성 : vi Readme.md (insert모드 전환 : i, 취소 : esc, 저장 : :wq)
- 로컬 저장소에 프로젝트 생성
- 로컬 저장소에 추가해놓기 : git add -A
- 커밋(+메시지) : git commit -m “init Project”
- 파일 올리기 : git push
- 브런치 생성 : git branch 브런치명
- 브런치 변경 : git checkout 브런치명
- 브런치에서 올리기 : git add -A
git commit -m "메시지"
git push --set-upstream 로컬저장소 브런치명 (로컬을 브런치로 올리기)
- master 브런치 합병 : git checkout 마스터브런치명
git merge 브런치명
git push
git pull 로컬저장소명 마스터브런치명
Branch : test
Material-NoActionBar
themes.xml
<style name="Theme.MyApp" parent="Theme.MaterialComponents.Light.NoActionBar"> <!-- Primary brand color. --> <item name="colorPrimary">@color/purple_500</item> <item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorOnPrimary">@color/white</item> <!-- Secondary brand color. --> <item name="colorSecondary">@color/teal_200</item> <item name="colorSecondaryVariant">@color/teal_700</item> <item name="colorOnSecondary">@color/black</item> <!-- Status bar color. --> <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> <!-- Customize your theme here. --> </style>
AndroidManifest.xml
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.MyApp">
Branch : 01_ui
Kotlin-Extension-Functions
Extensions.kt
fun EditText.onMyTextChanged(completion : (Editable?) -> Unit){ this.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { // 's' 문자열에서 "start" 위치로 부터 "count" 길이만큼 "after"로 변경될 예정임을 알림 } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { // 's'가 start 위치로 부터 count 길이만큼 변경되었다는 것을 알려준다. 이전 문자열에서 before 길이만큼 변경되었다는 것을 알린다. } override fun afterTextChanged(editable: Editable?) { // 's' 내의 어떤 문자열이 변경되었다는 것을 알려준다. completion(editable) } }) }
HomeActivity.kt
fun EditText.onMyTextChanged(completion : (Editable?) -> Unit){ this.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { // 's' 문자열에서 "start" 위치로 부터 "count" 길이만큼 "after"로 변경될 예정임을 알림 } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { // 's'가 start 위치로 부터 count 길이만큼 변경되었다는 것을 알려준다. 이전 문자열에서 before 길이만큼 변경되었다는 것을 알린다. } override fun afterTextChanged(editable: Editable?) { // 's' 내의 어떤 문자열이 변경되었다는 것을 알려준다. completion(editable) } }) }
Material-Theme-Handle
activity_home
<RadioGroup android:id="@+id/search_radioGroup" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="30dp" android:orientation="horizontal" app:layout_constraintBottom_toTopOf="@+id/search_text_layout" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/imageView3"> <!-- 메테리얼 사진검색 라디오버튼 --> <com.google.android.material.radiobutton.MaterialRadioButton android:id="@+id/radiobutton_photo" android:layout_width="match_parent" android:layout_height="wrap_content" android:checked="true" android:text="@string/Photo_search" /> <!-- 메테리얼 사용자검색 라디오버튼 --> <com.google.android.material.radiobutton.MaterialRadioButton android:id="@+id/radiobutton_user" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="20dp" android:checked="false" android:text="@string/User_search" /> </RadioGroup> <com.google.android.material.textfield.TextInputLayout android:id="@+id/search_text_layout" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="30dp" android:hint="@string/Photo_search" app:boxStrokeColor="@color/design_default_color_primary" app:counterEnabled="true" app:counterMaxLength="15" app:endIconMode="clear_text" app:helperText="@string/Enter_search_word" app:layout_constraintBottom_toTopOf="@+id/include" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.075" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/search_radioGroup" app:shapeAppearance="@style/ShapeAppearance.MaterialComponents.MediumComponent" app:startIconDrawable="@drawable/ic_photo_library"> <!-- hint부터 : 클릭 시 힌트 출력, 입력 창 아래 설명추가, 글자 수 카운트 기능 활성 , 글자 수 설정, 입력창 시작부분에 아이콘, 클릭 시 입력창 색 변화, 입력시 클리어 버튼 --> <!-- 메테리얼 EditText --> <com.google.android.material.textfield.TextInputEditText android:id="@+id/search_edit_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:maxLength="15" /> </com.google.android.material.textfield.TextInputLayout>
Branch : 02_retrofit
retrofit-config
Extensions.kt
//문자열이 제이슨 형태인지. 제이슨 배열 형태인지 확인 fun String?.isJsonObject():Boolean { return this?.startsWith("{") == true && this.endsWith("}") // 위와 같으면 true 아니면 false } fun String?.isJsonArray():Boolean { return this?.startsWith("[") == true && this.endsWith("]") }
Constants.kt
enum class RESPONSE_STATE { OK, FAIL } object API { const val BASE_URL = "https://api.unsplash.com/" const val CLIENT_ID = "YOUR ACCESS KEY" const val SEARCH_PHOTOS = "search/photos" const val SEARCH_USERS = "search/users" }
enum class의 상수들은 객체로 취급하며, 레트로핏 통신의 성공 유무를 구분하기 위해서 선언
retrofit으로 네트워크 통신에 사용할 기본 자료형만 사용할 수 있는 상수를 object로 class를 정의(싱글턴 패턴)
//람다식으로 response\_state와 string을 Unit type(리턴값이 없이 없음을 나타냄)
<br>
* IRetrofit_Service.kt
'''kotlin
interface IRetrofit_Service {
@GET(API.SEARCH_PHOTOS)
fun serachPhotos(@Query("query") searchTerm : String) : Call<JsonElement>
// 사진 검색 서비스 http://www.unsplash.com/serach/photos/?query=입력값
@GET(API.SEARCH_USERS)
fun searchUsers(@Query("query") searchTerm : String) : Call<JsonElement>
// 사용자 검색 서비스
}
레트로핏은 HTTP API를 인터페이스로 변환해준다. @GET Annotation을 이용해 인수로 전달한 API.SEARCH_PHOTOS URL에 @Query Annotation을
사용해 URL feild를 나타내는 key에 value값을 추가하여 HTTP Request를 처리하도록 하고 반환되는 타입을 Call<객체타입>로 한다.
RetrofitClient.kt
object RetrofitClient { // 레트로핏 클라이언트 선언 private var retrofitClient: Retrofit? = null // private lateinit var retrofitClient: Retrofit (선택사항) // 레트로핏 클라이언트 가져오기 fun getClient(baseUrl: String): Retrofit? { Log.d(Constants.TAG, "RetrofitClient - getClient() called") //로깅 인터셉터 추가(OkHTTP 인스턴스 생성) val client = OkHttpClient.Builder() // 로그를 찍기 위한 로깅 인터셉터 설정 val loggingInterceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger { override fun log(message: String) { when { message.isJsonObject() -> Log.d( Constants.TAG, "RetrofitClient - getClient() - log() / message : ${JSONObject(message).toString(4)}" ) message.isJsonArray() -> Log.d( Constants.TAG, "RetrofitClient - getClient() - log() / message : ${JSONObject(message).toString(4)}" ) else -> { try { Log.d( Constants.TAG, "RetrofitClient - getClient() - log() / message : ${JSONObject(message).toString(4)}" ) } catch (e: Exception) { Log.d( Constants.TAG, "RetrofitClient - getClient() - log() / error : $e, message : $message" ) } } } } }) // 위에서 생성한 로깅 인터셉터를 okhttp client에 추가 client.addInterceptor(loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)) // 기본 파라미터 인터셉터 설정 val baseParameterInterceptor = (object : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { Log.d(Constants.TAG, "RetrofitClient - getClient() interceptor() / chain : $chain") //원래의 요청(baseParmeter가 들어가기 전의 요청) val originalRequest = chain.request() //Query Parameter 추가(CLIENT_ID를 Url에 추가) val addClientIdUrl = originalRequest.url.newBuilder().addQueryParameter("client_id", API.CLIENT_ID).build() val finalRequest = originalRequest.newBuilder() .url(addClientIdUrl) .method(originalRequest.method, originalRequest.body) .build() return chain.proceed(finalRequest) } }) // 위에서 생성한 기본 파라미터 인터셉터를 okhttp clent에 추가 client.addInterceptor(baseParameterInterceptor) // 커넥션 타임아웃 client.connectTimeout(3000, TimeUnit.MILLISECONDS) //커넥션 작업의 타임아웃 client.readTimeout(3000, TimeUnit.MILLISECONDS) //읽기 작업의 타임아웃 client.writeTimeout(3000, TimeUnit.MILLISECONDS) //쓰기 작업의 타임아웃 client.retryOnConnectionFailure(false) //실패할 경우 다시 시도할 것인가?
```
//빌더를 통해 인스턴스 생성
if (retrofitClient == null) {
retrofitClient = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(baseUrl)
// 위에서 설정한 client로 retrofit client를 설정한다.
.client(client.build())
.build()
}
return retrofitClient
}
}
<br>
## OkHttp-HttpLoggingInterceptor
<https://square.github.io/okhttp/interceptors/>
#### val client = OkHttpClient.Builder() : OkHttpClient.Builder() 클래스를 사용해 인스턴스를 생성
#### val loggingInterceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger { ... }) : 네트워크 요청과 응답에 대한 로그를 볼 수 있다.
<br>
#### -NONE : 로그가 없습니다.
#### -BASIC : 요청 및 응답 라인을 기록합니다.
#### -HEADERS : 요청 및 응답 라인과 해당 헤더를 기록합니다.
#### -BODY : 요청 및 응답 라인과 해당 헤더 및 본문 (있는 경우)을 기록합니다.
<br>
#### client.addInterceptor(loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)) : 생성한 client에 interceptor를 추가
<br>
## OkHttp-Interceptor
#### 요청에 대해 Header 또는 Parameter 등을 재작성할 수 있다.
#### 응답으로 온 chain 객체를 이용해 요청에 대해 공통적인 data를 달아줄 때 유용하고 Retrofit Api 인터페이스 정의 시 @Header Annotation을 달아줄 필요가 없어진다.
#### return chain.proceed(finalRequest) : proceed()메서드로 서버에 통신하고 응답을 받아온다.
<br><br>
## Retrofit Client
#### Retrofit을 이용해 REST API에 네트워크 요청을 보내기위해
#### retrofitClient = Retrofit.Builder() : Retrofit.Builder 클래스를 사용해 인스턴스를 생성
#### .addConverterFactory(GsonConverterFactory.create()) : json 응답을 parsing하여 객체로 변환하기 위한 컨버터
#### .baseUrl(baseUrl) : 요청할 서버의 기본 URL을 지정
#### .client(client.build()) : Interceptor로 생성한 OkHttpClient를 요청에 사용할 수 있도록 해준다.
#### .build() : Retrofit 객체 생성
<br>
* IRetrofit_Service.kt
~~~kotlin
class RetrofitManager {
companion object {
val instance = RetrofitManager()
// 객체생성
}
// http call 생성
// 레트로핏 인터페이스 가져오기
private val iretrofitService : IRetrofit_Service? = RetrofitClient.getClient(API.BASE_URL)?.create(IRetrofit_Service::class.java)
//사진검색 api 호출
fun searchPhotos(searchTerm: String?, completion: (RESPONSE_STATE, String) -> Unit){
val term = searchTerm.let { it } ?: ""
val call = iretrofitService?.serachPhotos(searchTerm = term).let { it } ?: return
call.enqueue(object : retrofit2.Callback<JsonElement> {
override fun onResponse(call: Call<JsonElement>, response: Response<JsonElement>) {
Log.d(Constants.TAG, "RetrofitManager - onResponse() called : response.raw(): ${response.raw()}")
Log.d(Constants.TAG, "RetrofitManager - onResponse() called : response.body(): ${response.body()}")
completion(RESPONSE_STATE.OK, response.raw().toString())
}
override fun onFailure(call: Call<JsonElement>, t: Throwable) {
Log.d(Constants.TAG, "RetrofitManager - onFailure() called : t: $t")
completion(RESPONSE_STATE.FAIL, t.toString())
}
})
}
}
Network Request
enqueue() : 비동기 Request를 보내고 Response가 돌아오면 콜백으로 앱에게 알려준다. 네트워크에 대한 작업은 비동기로 작업하도록 Background 스레드에서 처리해야 합니다.
onResponse(), onFailure() 콜백 메소드를 구현해야합니다. 수행한 Request에 대한 Response를 전달받아 처리할 수 있습니다.
High-Order-Function
일반적으로 함수는 매개변수와 반환값이 데이터지만, 고차함수는 매개변수로 함수를 전달받거나, 함수를 반환하는 함수를 말합니다.
https://androidtest.tistory.com/116
https://taehyungk.github.io/posts/android-kotlin-high-order-function/
'Project' 카테고리의 다른 글
09_mvvm : Localization, Coroutine (0) | 2022.03.05 |
---|---|
09_mvvm : Network communication using retrofit - Search User Test (0) | 2021.02.23 |