관리 메뉴

막내의 막무가내 프로그래밍 & 일상

[안드로이드] 코틀린 유용한 확장함수(let, with, apply, run) 예제 정리 본문

안드로이드/코틀린 & 아키텍처 & Recent

[안드로이드] 코틀린 유용한 확장함수(let, with, apply, run) 예제 정리

막무가내막내 2021. 1. 20. 20:32
728x90

 

[2021-04-14 업데이트]

[2021.01.14 블로그 포스팅 스터디 여덟번째(마지막) 글]

 

글에 이상한 점이 많아

2021.06.28 에 포스팅을 다시 작성했습니다. 

다음 링크로 가주시길 바랍니다 :)

https://youngest-programming.tistory.com/142

 

[안드로이드] 코틀린 범위 지정 함수, 고차 함수 (let, apply, with, run)

[2021-04-13 업데이트] 출처 :커니의 코틀린(강추!) 밑 링크에 예제를 좀 더 자세히 포스팅 했습니다. 참고해주세요 :) youngest-programming.tistory.com/480 [코틀린] 코틀린 유용한 확장함수(let, with, apply,..

youngest-programming.tistory.com

 

 

 


youngest-programming.tistory.com/142

 

[코틀린] 범위 지정 함수, 고차 함수 (let, apply, with, run)

출처 :커니의 코틀린(강추!) let() fun T.let(block: (T) -> R) : R 이 함수를 호출하는 객체를 이어지는 함수형 인자 block의 인자로 전달하며, block 함수의 결과를 반환한다. ex) fun letEx(str : String?){ if..

youngest-programming.tistory.com

이전에 위 링크에서 간략하게 정리한 적이 있는데 좀 햇갈리게 정리한 것 같아서 직접 예제 코드를 작성하며 다시한번 복습을 하려고 합니다.

 

간단한 설명과 예제를 함께보면 이해가 가실거라고 믿습니다... !

 

 

 

1. let()   

fun<T, R> T.let(block : (T) -> R) : R

let() 함수는 자기 자신을 인수로 전달하고 수행된 결과를 반환합니다. 인수로 전달한 객체는 it으로 참조합니다. 주로 ? 연산자와 함께 사용하여 null이 아닐때만 실행하는 코드로 자주 사용합니다. (자바의 if(it != null) 문을 대체한다고 보면 됩니다.)

var str: String? = null
fun main(args: Array<String>) {
    str = "1234"
    val result = str?.let {
        // str 이 null 아닌 경우에만 블록 실행
        Integer.parseInt(it) //마지막 결과값(int)를 반환해준다.
    }
    println(result)

    str?.let {
        it + "aaaa" //자기자신(리시버)에는 값이 적용되지 않는다는 점에 주의
    }
    println(str)
}
//결과 
//1234
//1234

 

 

 

2. with()   

fun<T, R> with(receiver : T, block T.() -> R) : R

with() 함수는 인수로 객체를 받고 블록에 리시버 객체로 전달합니다. 그리고 수행된 결과를 반환합니다. 리시버 객체로 전달된 객체는 this로 접근할 수 있으며 생략도 가능합니다. with()를 사용시 주의할 점은 null safe 호출이 불가능하므로 인자가 절대로 null 이 아닌게 확실할 경우에만 사용해야 합니다. 

fun main(args: Array<String>) {
    val str = "aBcD" //절대 null일수가 없다.
    val str2 = "MakNe " //절대 null일수가 없다.
    with(str) {
        println(this.toLowerCase())
    }
    with(str2) {
        //this 생략가능
        println(toLowerCase())
    }
}
//결과
//abcd
//makne 

 

 

추가로 저같은 경우는 이 함수를 다음과 같이 액티비티 혹은 프래그먼트에서 Injection된 ViewModel의 라이브데이터 옵저빙하는데 주로 사용합니다. 

[Activity]

//injection되어 null일수가 없다.
private val viewModel: BoardViewModel by viewModel()

//뷰모델의 LiveData를 옵저빙하는 코드
private fun initViewModelCallback() {
        with(viewModel) {
            univAuthSuccess.observe(this@BoardActivity, Observer { success ->
                if (success) {
                    binding.layoutUnivAuth.visibility = View.GONE
                    binding.layoutBoard.visibility = View.VISIBLE
                } else {
                    showToast(getString(R.string.univ_auth_fail_msg))
                }
            })

            alreadyUnivAuth.observe(this@BoardActivity, Observer { isAlreadyAuth ->
                if (isAlreadyAuth) {
                    binding.layoutUnivAuth.visibility = View.GONE
                    binding.layoutBoard.visibility = View.VISIBLE
                }
            })

            goBoardList.observe(this@BoardActivity, Observer { boardName ->
                val intent = Intent(this@BoardActivity, BoardListActivity::class.java)
                intent.putExtra(EXTRA_BOARD_NAME, boardName)
                startActivity(intent)
            })
        }
    }

[ViewModel]

class BoardViewModel(private val repository: BoardRepository) : BaseViewModel() {
    private val _univAuthSuccess = SingleLiveEvent<Boolean>() //대학교인증 EditText
    private val _alreadyUnivAuth = SingleLiveEvent<Boolean>() //이미 대학교 인증 여부
    private val _goBoardList = SingleLiveEvent<String>() //해당 주제 대학교 게시판 이동

    val univAuthSuccess: LiveData<Boolean> get() = _univAuthSuccess
    val alreadyUnivAuth: LiveData<Boolean> get() = _alreadyUnivAuth
    val goBoardList: LiveData<String> get() = _goBoardList

 

 

 

3. apply()   

fun<T> T.apply(block : T.() -> Unit ) : T

apply() 함수는 블록에 객체 자신이 리시버 객체로 전달되고 이 객체가 반환됩니다. 객체의 상태를 변화시키고 그 객체를 다시 반환할 때 주로 사용합니다.

data class Person(var name: String, var age: Int)

fun main(args: Array<String>) {
    val person = Person("에이스", 20)
    val result = person.apply {
        //this(person) 생략가능
        name = "흰수염"
        age = 75
    }
    //person객체(리시버)가 변경되고 자기 자신을 리턴하기 때문에 
    //result 도 person과 같은 값을 갖는 객체를 가진다.
    println(result)
    println(person)
}
//결과
//Person(name=흰수염, age=75)
//Person(name=흰수염, age=75)

 

어댑터에서 사용했던 코드입니다. (onCreateViewHolder 부분)

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.mtjin.cnunoticeapp.R
import com.mtjin.cnunoticeapp.data.employ.EmployNotice
import com.mtjin.cnunoticeapp.databinding.ItemEmployBinding

class EmployAdapter(
    private val itemClick: (EmployNotice) -> Unit,
    private val numClick: (EmployNotice) -> Unit
) : ListAdapter<EmployNotice, EmployAdapter.ViewHolder>(
    diffUtil
) {
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding: ItemEmployBinding = DataBindingUtil.inflate(
            LayoutInflater.from(parent.context),
            R.layout.item_employ,
            parent,
            false
        )
        val viewHolder = ViewHolder(binding)
        binding.apply { //this 생략 가능
            root.setOnClickListener {
                itemClick(getItem(viewHolder.bindingAdapterPosition)) 
            }
            employTvNum.setOnClickListener {
                numClick(getItem(viewHolder.bindingAdapterPosition))
            }
        }
        return viewHolder
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position)) 
    }

    class ViewHolder(private val binding: ItemEmployBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(item: EmployNotice) {
            binding.item = item
            binding.executePendingBindings()
        }
    }

    companion object {
        val diffUtil = object : DiffUtil.ItemCallback<EmployNotice>() {
            override fun areContentsTheSame(oldItem: EmployNotice, newItem: EmployNotice) =
                oldItem == newItem

            override fun areItemsTheSame(oldItem: EmployNotice, newItem: EmployNotice) =
                oldItem.link == newItem.link
        }
    }
}


혹은 다음과 같은 방법도 가능합니다. (위와 다른 어댑터코드입니다.)

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding: ItemBoardBinding = DataBindingUtil.inflate(
            LayoutInflater.from(parent.context),
            R.layout.item_board,
            parent,
            false
        )
        return ViewHolder(binding).apply {
            binding.root.setOnClickListener { view ->
                val position = bindingAdapterPosition.takeIf { it != RecyclerView.NO_POSITION }
                    ?: return@setOnClickListener
                itemClick(items[position])
            }
        }
    }

 

 

 

 

 

 

4. run()   

1) fun<R> run(block : () -> R ) : R

2) fun<T, R> T.run(block : T.() -> R ) : R

run() 함수는 익명 함수처럼 사용하는 방법과 객체에서 호출하는 방법을 모두 제공합니다. 블록의 마지막 결과를 반환합니다. 블록안에 선언된 변수는 모두 임시로 사용되는 변수입니다. (함수안의 로컬변수같이 말이죠) 그래서 복잡한 계산이나 임시변수가 많이 필요할때 유용하게 사용됩니다.

 

1) 익명 함수

fun main(args: Array<String>) {
    val result = run {
        val name = "막내"
        val hobby = "블로그 운영"
        val age = 27
        "$name $hobby $age"
    }
    print(result)
    //print(name) 컴파일에러
}
//결과 : 막내 블로그 운영 27

 

2) 객체에서 호출

var str: String? = null
fun main(args: Array<String>) {
    str = "abCdEfG"
    val result = str?.run {
        toLowerCase()//this(str) 생략가능
    }
    println(result)
    println(str) // apply()와 다르게 자기자신이(리시버)의 값은 바뀌지 않는다는점에 주의
}
//결과
//abcdefg
//abCdEfG

 

 

4. also()   

1) fun<T> T.also(block: (T) -> Unit): T

let 함수와 비슷하나, 코드 블럭 수행 결과와 상관없이 T 객체 this를 반환합니다.

객체의 속성을 전혀 사용하지 않거나 변경하지 않고 사용하는 경우에 also를 사용합니다.

객체의 데이터 유효성을 확인하거나, 디버그, 로깅 등의 부가적인 목적으로 사용할 때에 적합합니다.

val tmpName ="Jack"
    val ex = tmpName.also {
        it.toUpperCase()
    }
    println(tmpName)
    println(ex)
    val person = Person(name = "Jack", age = 27)
    val result = person.also {
        it.name = "GrandFather"
        it.age = 70
    }
    println(person)
    println(result)
    val numbers = mutableListOf("one", "two", "three")
    numbers
            .also { println("numbers: $it") }
            .add("four")
    println(numbers)
    
    
    //결과

Jack
Jack
Person(name=GrandFather, age=70)
Person(name=GrandFather, age=70)
numbers: [one, two, three]
[one, two, three, four]

 

 

 

 

 

 

 

이상 코틀린 사용시 유용한 확장함수에 대해 알아봤습니다. 

 

 

블로그 포스팅 스터디는 처음 해봤는데 바쁘지만 벌금도 안내고 공부도하기 위해 나름 열심히 포스팅한 것 같아서 뿌듯하네요 ㅎㅎ 

 

 

댓글과 공감은 큰 힘이 됩니다. 감사합니다. !!!

 

 

 

 

 

728x90
Comments