본문 바로가기
개발(Development)/Android(안드로이드)

[안드로이드 kotlin] Fragment에서 Context 사용 방법: findViewById, runOnUiThread, Toast 등

by 카레유 2021. 7. 21.

보통 액티비티에서 Fragment 를 띄우는 경우,

액티비티 내부에서 사용할 수 있었던 메서드들이 Fragment에선 사용할 수 없는 경우가 많다.

또한 파라미터로 Context를 받는 메서드들도 어떻게 사용해야할지 난감할 수 있다.

 

Fragment클래스는 Context를 상속 받지 않기 때문에 발생하는 일이다.

 

즉, 일반적인 방법으로는 Fragment 내부에서 아래의 메서드들을 사용하기가 어렵다.

 

1. Context 에 정의된 메서드

- findViewById, runOnUIThread, getApplicationContext , getSystemService, startActivity 등

 

2. Context를 파라미터(매개변수)로 받는 메서드

- Toast 등


# Framgment에서 Context 사용 방법

핵심은 Context를 어디선가 취득하는 것이다.

 

Fragment클래스의 생명주기 콜백 메서드인 onAttach()의 매개변수로 Context가 들어온다.

또한 매개변수로 들어온 Context는 플래그먼트를 띄운 Activity로 형변환하여 사용할 수 있다.

 

따라서 Flagment의 전역변수(프로퍼티)로 선언하고, onAttach()에서 할당해두면

어디에서든 Context(액티비티)를 참조하여 사용할 수 있게 된다.

 

즉, 아래처럼 코드를 작성하면 된다.

 

1. Fragment클래스의 프로퍼티로 Context를 참조할 변수 선언

2. onAttach() 콜백메서드에서 Context를 MainActivity로 형변환하여 할당

 

class FragmentText : Fragment() {

    // 1. Context를 할당할 변수를 프로퍼티로 선언(어디서든 사용할 수 있게)
    lateinit var mainActivity: MainActivity

    override fun onAttach(context: Context) {
        super.onAttach(context)
        
        // 2. Context를 액티비티로 형변환해서 할당
        mainActivity = context as MainActivity
    }
}

 

이렇게 해두면,

Fragment에서도 runOnUiThread, findViewById, getApplicationCotext, Toast 등을 아래처럼 모두 사용할 수 있다.

 

1. Context 에 정의된 메서드

- mainActivity.findViewById

- mainActivity.runOnUIThread

- mainActivity.getApplicationContext

- mainActivity.getSystemService

- mainActivity.startActivity

 

2. Context를 파라미터(매개변수)로 받는 메서드

- Toast(mainActivity, "메세지", Toast.LENGTH_LONG)  등


# Fragment Context  사용 예제 코드

아래는 테스트 해본 코드 전문이다.

 

1. 레이아웃 XML파일에 TextView, Button 하나씩 생성해두고,

2. onAttach() 에서 Context 를 받아오고,

3. onStart() 콜백 메서드에서 사용한다.

  - TextView를 참조해서 스레드를 통해 3초후에 내용이 바뀌게 하고,

  - Button을 참조해서 클릭하면 Toast가 뜨도록 구현했다.

 

class FragmentText : Fragment() {

    // 1. Context를 받아올 변수 선언
    lateinit var mainActivity: MainActivity

    override fun onAttach(context: Context) {
        super.onAttach(context)
        
        // 2. Context를 Activity로 형변환하여 할당
        mainActivity = context as MainActivity
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        return inflater.inflate(R.layout.fragment_life, container, false)
    }

    override fun onStart() {
        super.onStart()

        // Button을 참조해서 클릭 리스너 달기
        mainActivity.findViewById<Button>(R.id.buttonTest).setOnClickListener {
            Toast.makeText(mainActivity, "플래그먼트에서도 Toast가 됩니다", Toast.LENGTH_LONG).show()
        }

        // TextView를 참조해서 텍스트 바꾸기
        mainActivity.findViewById<TextView>(R.id.textViewTest).text = "처음"

        // 스레드 돌리기
        thread(start = true){
            Thread.sleep(3000)
            
            // 3초후에 runOnUiThread를 통해 TextView의 텍스트 바꾸기
            mainActivity.runOnUiThread {
                mainActivity.findViewById<TextView>(R.id.textViewTest).text = "3초후에 바뀌었네요"
            }
        }
    }

}

 

 

- 실행 결과

 

 


주의할 점은

onCreateView에서 mainActivity.findViewById()메서드를 통해 Button 등의 리소스를 취득하려 하면,

참조를 못해서 null 오류가 발생할 수 있다는 것이다.(아직 액티비티에게 전달되지 않은 생태라 시간차가 있다)

 

이 경우에는 아래와 같이 레이아웃을 inflate하여 View 객체에 할당한 다음 findViewById()를 호출하면 된다.

 

class FragmentText : Fragment() {

    // 1. Context를 받아올 변수 선언
    lateinit var mainActivity: MainActivity

    override fun onAttach(context: Context) {
        super.onAttach(context)
        
        // 2. Context를 Activity로 형변환하여 할당
        mainActivity = context as MainActivity
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

       	// 1. 레아이웃을 inflate()하여 view를 변수에 저장
        val view = inflater.inflate(R.layout.fragment_life, container, false)
        
        // 2. view객체에서 findViewById()메서드 호출
        view.findViewById<TextView>(R.id.textViewTest).text = "처음"
        view.findViewById<Button>(R.id.buttonTest).setOnClickListener {
            Toast.makeText(mainActivity, "플래그먼트에서도 Toast가 됩니다", Toast.LENGTH_LONG).show()
        }
        
        // 3. 레이아웃 view를 액티비티에게 반환
        return view
    }

}

 

또는 개발환경에 따라 onStart() 등에서 mainActiviy.findViewById()를 사용하여 리소스를 참조해서 사용하면 될듯 하다.

 

그리고 사실 이 문제는 뷰바인딩을 사용하면 깔끔하게 해결된다.

 

[안드로이드 Kotlin] Fragment 에서 뷰바인딩(View Binding) 사용 방법

 

 

댓글