코틀린 안드로이드 앱개발 강좌, 프래그먼트 – 액티비티처럼 동작하는 뷰

안드로이드 앱 개발에서 프래그먼트(Fragment)는 매우 중요한 컴포넌트입니다. 프래그먼트는 사용자 인터페이스(UI)의 일부를 구성하고, 독립적으로 생명 주기를 관리할 수 있는 모듈입니다. 프래그먼트를 사용하면 다양한 화면 사이에서 전환하며, 재사용 가능한 UI 구성 요소를 만들어낼 수 있습니다. 이 강좌에서는 코틀린을 사용하여 프래그먼트를 활용하는 방법에 대해 자세히 알아보겠습니다.

프래그먼트의 개념

프래그먼트는 액티비티 내에서 UI의 작은 부분을 포함하는 컴포넌트입니다. 액티비티는 사용자와 상호 작용하는 단일 화면을 나타내지만, 하나의 액티비티 내에서 다양한 프래그먼트를 통해 여러 UI를 구성할 수 있습니다. 프래그먼트는 다음과 같은 점에서 매우 유용합니다:

  • 재사용 가능: 동일한 프래그먼트를 다양한 액티비티에서 사용할 수 있습니다.
  • 모듈화: UI의 특정 부분을 별도의 클래스로 만들 수 있습니다.
  • 유연성: 화면 크기와 방향에 따라 동적으로 추가, 제거 또는 대체할 수 있습니다.

프래그먼트의 생명주기

프래그먼트는 자신만의 생명주기를 가집니다. 프래그먼트의 생명주기는 액티비티의 생명주기에 의존하지만, 독립적으로 생명주기 메소드를 호출합니다. 프래그먼트의 주요 생명주기 메소드는 다음과 같습니다:

  • onAttach(): 프래그먼트가 액티비티에 연결될 때 호출됩니다.
  • onCreate(): 프래그먼트의 초기화 작업을 수행할 때 호출됩니다.
  • onCreateView(): 프래그먼트의 UI를 구성할 때 호출됩니다.
  • onActivityCreated(): 액티비티의 생성이 완료된 후 호출됩니다.
  • onStart(): 프래그먼트가 사용자에게 보이기 시작할 때 호출됩니다.
  • onResume(): 사용자와의 상호작용이 가능할 때 호출됩니다.
  • onPause(): 프래그먼트가 사용자와의 상호작용을 중지할 때 호출됩니다.
  • onStop(): 프래그먼트가 더 이상 사용자에게 보이지 않을 때 호출됩니다.
  • onDestroyView(): 프래그먼트의 UI가 파괴될 때 호출됩니다.
  • onDetach(): 프래그먼트가 액티비티와 분리될 때 호출됩니다.

기본 프래그먼트 생성하기

이제 코틀린을 사용하여 간단한 프래그먼트를 만들어 보겠습니다. 프로젝트를 새로 시작하고, 아래 단계를 따라 진행해 주세요.

1. 프래그먼트 클래스 생성

프래그먼트를 생성하기 위해 Kotlin 클래스를 만들고, Fragment를 상속합니다. 예를 들어, SampleFragment라는 이름의 프래그먼트를 생성합니다:

package com.example.myapp

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment

class SampleFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, 
        container: ViewGroup?, 
        savedInstanceState: Bundle?
    ): View? {
        // 프래그먼트에서 사용할 레이아웃파일을 인플레이트 합니다.
        return inflater.inflate(R.layout.fragment_sample, container, false)
    }
}

2. 레이아웃 파일 생성

프래그먼트에서 사용하는 레이아웃 XML 파일을 생성합니다. res/layout/fragment_sample.xml 파일을 다음과 같이 작성합니다:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/sampleTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello, Fragment!" />

</LinearLayout>

3. 액티비티에서 프래그먼트 사용하기

이제 생성한 프래그먼트를 액티비티에서 사용할 준비가 되었습니다. 액티비티의 레이아웃 파일에 FrameLayout을 추가하여 프래그먼트를 표시할 공간을 만듭니다:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

4. 프래그먼트를 액티비티에 추가하기

액티비티의 onCreate 메소드에서 프래그먼트를 동적으로 추가합니다:

package com.example.myapp

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentManager

class MainActivity : AppCompatActivity() {

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

        // FragmentManager를 이용하여 Fragment를 추가합니다.
        if (savedInstanceState == null) {
            val fragment = SampleFragment()
            supportFragmentManager.beginTransaction()
                .add(R.id.fragment_container, fragment)
                .commit()
        }
    }
}

프래그먼트 간 전환

이제 여러 프래그먼트를 만들어 서로 전환하는 방법을 알아보겠습니다. 간단한 예로, 버튼을 클릭하면 다른 프래그먼트로 전환되는 기능을 구현해 보겠습니다.

1. 두 번째 프래그먼트 생성

두 번째 프래그먼트 SecondFragment를 생성하고, 버튼이 포함된 레이아웃을 추가합니다:

package com.example.myapp

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_second.*

class SecondFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, 
        container: ViewGroup?, 
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_second, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // 버튼 클릭 시 프래그먼트를 전환합니다.
        button.setOnClickListener {
            val firstFragment = SampleFragment()
            fragmentManager?.beginTransaction()
                ?.replace(R.id.fragment_container, firstFragment)
                ?.addToBackStack(null)
                ?.commit()
        }
    }
}

2. 두 번째 프래그먼트 레이아웃 파일 생성

아래와 같이 두 번째 프래그먼트의 레이아웃 파일을 생성합니다:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Back to Sample Fragment"/>

</LinearLayout>

3. 프래그먼트 전환 추가

프래그먼트를 전환하는 메소드를 기존의 SampleFragment에 추가하여 버튼 클릭 시 두 번째 프래그먼트를 표시하는 방법도 구현합니다:

class SampleFragment : Fragment() {
    // 생략...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // 버튼 클릭 시 두 번째 프래그먼트로 전환
        val button = view.findViewById

컬렉션과 데이터 전송하기

프래그먼트 간의 데이터 전송과 컬렉션을 사용하는 방법에 대해 알아보겠습니다. 프래그먼트에 데이터를 전달하는 일반적인 방법은 Bundle을 사용하는 것입니다. 아래 예제에서는 검색어를 입력받고, 그 검색어를 사용하여 새로운 프래그먼트를 만듭니다.

1. 사용자 입력을 위한 EditText 추가

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/searchEditText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="검색어를 입력하세요"/>

    <Button
        android:id="@+id/searchButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="검색"/>

</LinearLayout>

2. 검색어를 전달하는 메소드 작성

키워드를 입력받아 두 번째 프래그먼트로 전달하는 메소드를 작성합니다:

searchButton.setOnClickListener {
    val keyword = searchEditText.text.toString()
    val searchFragment = SearchFragment()
    
    // Bundle을 사용하여 데이터 전달
    val bundle = Bundle()
    bundle.putString("keyword", keyword)
    searchFragment.arguments = bundle
    
    // 프래그먼트 전환
    fragmentManager?.beginTransaction()
        ?.replace(R.id.fragment_container, searchFragment)
        ?.addToBackStack(null)
        ?.commit()
}

3. 데이터 수신 및 표시

SearchFragment에서 전달받은 데이터를 수신하고 표시합니다:

class SearchFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, 
        container: ViewGroup?, 
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_search, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Bundle에서 데이터 수신
        val keyword = arguments?.getString("keyword")

        // 데이터 표시
        val textView = view.findViewById(R.id.resultTextView)
        textView.text = if (keyword != null) "검색어: $keyword" else "검색어가 없습니다."
    }
}

프래그먼트와 ViewModel을 이용한 데이터 관리

프래그먼트에서 ViewModel을 사용하여 데이터를 관리하는 방법을 알아보겠습니다. ViewModel은 액티비티와 프래그먼트에서 UI 관련 데이터를 저장하고 관리할 수 있는 클래스로, 생명주기에 따라 데이터를 유지하는 데 유용합니다.

1. ViewModel 클래스 생성

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class MyViewModel : ViewModel() {
    val data: MutableLiveData = MutableLiveData()
}

2. 프래그먼트에서 ViewModel 사용하기

프래그먼트에서 ViewModel을 생성하고, 데이터를 관찰하여 UI를 업데이트하는 방법을 보여줍니다:

import androidx.fragment.app.activityViewModels

class SampleFragment : Fragment() {
    private val viewModel: MyViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // ViewModel의 데이터 관찰
        viewModel.data.observe(viewLifecycleOwner) { newData ->
            // UI 업데이트
            textView.text = newData
        }

        // 데이터를 변경하여 UI를 업데이트
        button.setOnClickListener {
            viewModel.data.value = "새로운 데이터"
        }
    }
}

프래그먼트와 내비게이션

안드로이드 내비게이션 컴포넌트를 사용하여 프래그먼트 간 전환을 보다 쉽게 관리할 수 있습니다. 내비게이션 컴포넌트를 사용하면 프래그먼트와 액티비티 간의 전환을 복잡하지 않게 처리할 수 있습니다.

1. 내비게이션 그래프 설정

내비게이션 그래프를 생성하고 프래그먼트를 추가합니다. 프래그먼트 간의 전환을 그래프에 정의합니다. 이러한 정의를 통해 코드의 양을 줄이고, 전환 애니메이션 및 백 스택 관리를 자동으로 처리할 수 있습니다.

2. 내비게이션 아키텍처로 프래그먼트 전환하기

val navHostFragment = supportFragmentManager
        .findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController

    // 버튼 클릭 시 프래그먼트 전환
    button.setOnClickListener {
        navController.navigate(R.id.action_sampleFragment_to_secondFragment)
    }

결론

이번 강좌에서는 안드로이드 앱 개발에서 프래그먼트의 개념과 사용 방법에 대해 알아보았습니다. 프래그먼트를 통해 UI를 재사용 가능하고 모듈화된 형태로 구성할 수 있으며, 생명주기를 관리하는 방법, 데이터 전송 및 ViewModel을 활용한 데이터 관리를 배웠습니다. 내비게이션 컴포넌트를 이용하면 프래그먼트 간의 전환이 더욱 수월해집니다. 이러한 내용을 바탕으로 실제 프로젝트에 적용해 보시기 바랍니다.

참고 자료