본문 바로가기

안드로이드

[안드로이드] 예제: MVVM+AAC를 이용한 RecyclerView 8 - Navigation 컴포넌트 활용

개요

이번 문서에서는 과거 문서에서 다룬적이 있는 navigation 컴포넌트를 활용하여 아래와 같은 UI로 바꿀 예정이다. 첫번째 프래그먼트의 영화 아이템을 클릭하면 다이얼로그가 나오고, 다이얼로그의 추가 버튼을 클릭하면 보고 싶은 영화 목록에 추가된다. 보고 싶은 영화 목록은 두번째 프래그먼트이다. 그리고 추가로 이전 문서의 경우 리사이클러뷰의 아이템의 overview를 보면 글이 오른쪽 끝에서 잘린 문제가 있었는데 이 또한 해결한다.

 

 

개발과정

  1. 프래그먼트 생성
  2. 네비게이션 설정 및 생성
  3. 리사이클러뷰 어답터 설정
  4. 레이아웃 픽스

1. 프래그먼트 생성

 

프래그먼트의 전반적인 로직은 기존 MainActivity와 같아야 한다. 영화 목록을 보여주는게 핵심 역할이다. 주목할 부분은 onCreateView 부분이다. 파스칼 방식으로 생성된 FragmentFirstBinding 객체를 반환한다. 그리고 다이얼로그를 보여주는 함수에서 추가 버튼을 클릭시 DB에 데이터를 저장하고 SecondFragment로 이동한다.

 

FirstFragment.kt

class FirstFragment : Fragment() {
    private lateinit var binding: FragmentFirstBinding
    val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)


    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = DataBindingUtil.inflate<FragmentFirstBinding>(inflater,
            R.layout.fragment_first,container,false)
        return binding.root
    }


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.lifecycleOwner = this
        binding.viewModel = viewModel
        setRecyclerView()

    }
    private fun deleteDialog(movie: Movie) {
        val builder = AlertDialog.Builder(this.context!!)
        builder.setMessage("Delete selected contact?")
            .setNegativeButton("취소") { _, _ -> }
            .setPositiveButton("추가") { _, _ ->
                val todo = Todo(null, movie.title, movie.overview, movie.poster_path)
                lifecycleScope.launch(Dispatchers.IO){viewModel.insert(todo)}
                val direction: NavDirections = FirstFragmentDirections.actionFirstFragmentToSecondFragment()
                findNavController().navigate(direction)
            }
        builder.show()
    }

    private fun setRecyclerView(){
        ...
    }
}

두번째 프래그먼트에서는 간단히 room db의 데이터 목록만 출력한다. 하나 주목할 점은 First, Second Fragment의 Adapter가 서로 다르다는 점이다. 이는 사실 구현을 하다보면 당연한 것이다.

 

SecondFragment.kt

class SecondFragment : Fragment() {
    private lateinit var binding: FragmentSecondBinding
    val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = DataBindingUtil.inflate<FragmentSecondBinding>(inflater,
            R.layout.fragment_second,container,false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.lifecycleOwner = this
        binding.viewModel = viewModel
        setRecyclerView()

    }
    private fun deleteDialog(todo: Todo) {
        val builder = AlertDialog.Builder(this.context!!)
        builder.setMessage("Delete selected contact?")
            .setNegativeButton("취소") { _, _ -> }
            .setPositiveButton("편집") { _, _ ->
            }
        builder.show()
    }

    private fun setRecyclerView(){
        // Set contactItemClick & contactItemLongClick lambda
        val adapter =
            TodoAdapter({ todo -> deleteDialog(todo) },
                { todo -> deleteDialog(todo) })

        binding.recyclerView.adapter=adapter
        binding.recyclerView.setHasFixedSize(true)
    }
}

2. 네비게이션 설정 및 생성

res 디렉토리에 navigation 리소스 디렉토리를 생성하고 nav_main.xml 네비게이션 리소스 파일을 생성한다. 그리고 이전 문서에서 한 방식 그대로 프래그먼트 두개를 추가한다. 그리고 첫번째 프래그먼트에서 두번재 프래그먼트로 이동하는 액션을 추가한다.

종속성 설정은 아래 문서를 참고
 

[안드로이드] 7. Navigation

Navigation 안드로이드 Jetpack의 네비게이션은 기존의 액티비티와 프래그먼트 사이의 복잡한 코드 구성을 간결하게 바꿀 수 있는 라이브러리이다. 또한 네비게이션 그래프를 통해 화면간의 구조를

bb-library.tistory.com

 

FirstFragment를 보면 다이얼로그 함수에 아래와 같은 코드가 존재한다. 이는 위에서 생성한 액션에 의해 생성된 객체이며 그 객체를 이용해 프래그먼트를 전환하는 코드이다.

val direction: NavDirections = FirstFragmentDirections.actionFirstFragmentToSecondFragment()
findNavController().navigate(direction)

3. 리사이클러뷰 어답터 설정

보고싶은 영화 목록을 위한 TodoAdapter를 추가한다. 그리고 item_todo.xml을 생성하여 item_movie와 유사하게 코딩하면 된다. 네이밍이 다소 어색하지만 기존의 Todo 네이밍을 그대로 가져가기 위해 사용한다. 그리고 바인딩 어답터를 아래와 같이 하나 더 추가해준다. 이름도 살짝 변경한다.

 

RecyclerViewBindingAdapter.kt

object RecyclerViewBindingAdapter {
    @BindingAdapter("movieData")
    @JvmStatic
    fun bindMovieData(recyclerView: RecyclerView, movies: List<Movie>?){
        val adapter = recyclerView.adapter as MovieAdapter
        adapter.submitList(movies)
    }

    @BindingAdapter("todoData")
    @JvmStatic
    fun bindTodoData(recyclerView: RecyclerView, todos: List<Todo>?){
        val adapter = recyclerView.adapter as TodoAdapter
        adapter.submitList(todos)
    }
}

TodoAdapter와 item_todo, movie는 이제 혼자 스스로 구현할 수 있다.


4. 레이아웃 픽스

저번 문서까지의 overview를 보면 글이 오른쪽 끝까지 나가며 잘린 것을 볼 수 있다. 이는 textView 코드 안에 다음 코드를 추가해주면 해결 가능하다.

app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"

글이 변경된 모습


다음 문서

다음 문서에서는 영화 아이템을 클릭 시 영화 상세 정보를 보여주는 프래그먼트를 생성할 예정이다. 이때도 navigation 컴포넌트를 충분히 활용할 것이다.