본문 바로가기
Android Application/앱 설계

안드로이드 Repository pattern 간단 예시(with MVVM pattern)

by sdchjjj 2023. 4. 3.
728x90

안녕하세요.

이번 포스팅에서는 repository pattern을 간략하게 적용한 예시를 구현해 보겠습니다.

UI의 표시 영역은 MVVM 패턴을 적용하여, MVVM + Repository pattern 구성이 됩니다.

 

저는 아래의 포스팅에서 MVVM 소스를 가져오겠습니다.

https://it-of-fortune.tistory.com/27

 

안드로이드 MVVM 패턴 예시

안녕하세요. 이번 포스팅에서는 소프트웨어 디자인 패턴 중 하나인 mvvm 패턴을 안드로이드 앱을 통해 알아보겠습니다. MVVM 패턴에서 M은 Model, V는 View, VM은 ViewModel을 의미합니다. 쉽게 말해 V는 UI

it-of-fortune.tistory.com

 

언어: 코틀린

sdk vsersion

  - compile: 33

  - min: 21

  - target: 33

 

우선 프래그먼트와 데이터 클래스 쪽은 다르지 않습니다.

fragment_pet.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="viewModel"
            type="com.contents.laboratory.architecture.PetViewModel" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".PetFragment">

        <Button
            android:id="@+id/button_cat"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="고양이"
            android:onClick="@{() -> viewModel.onCatClicked()}"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toStartOf="@id/button_dog" />

        <Button
            android:id="@+id/button_dog"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="강아지"
            android:onClick="@{() -> viewModel.onDogClicked()}"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/button_cat" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="5dp"
                    android:text="이름:"/>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@{viewModel.pet.name}"/>

            </LinearLayout>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="5dp"
                    android:text="나이:"/>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@{Integer.valueOf(viewModel.pet.age).toString()}"/>

            </LinearLayout>

        </LinearLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 

PetFragment.kt

@AndroidEntryPoint
class PetFragment : Fragment() {

    private var _binding: FragmentPetBinding? = null
    private val binding get() = _binding!!

    private val viewModel: PetViewModel by viewModels()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentPetBinding.inflate(layoutInflater, container, false).apply {
            viewModel = this@PetFragment.viewModel
            lifecycleOwner = viewLifecycleOwner
        }
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

 

Pet.kt

data class Pet(
    val name: String,
    val age: Int
)

 

DataSource class를 remote와 local로 나누겠습니다.

PetRemoteDataSource.kt

class PetRemoteDataSource @Inject constructor() {
    fun getCat(): Pet {
        return Pet(
            name = "키티",
            age = 5
        )
    }
}

PetLocalDataSource.kt

class PetLocalDataSource @Inject constructor() {
    fun getDog(): Pet {
        return Pet(
            name = "빙고",
            age = 6
        )
    }
}

실제 remote와 local 데이터를 가져오는 것이 아니고 단지 표현만 cat은 remote에서 dog는 local에서 가져오는 것으로 구현했습니다.

 

Repository class를 생성합니다.

PetRepository.kt

class PetRepository @Inject constructor(
    private val petRemoteDataSource: PetRemoteDataSource,
    private val petLocalDataSource: PetLocalDataSource
) {
    fun getCat(): Pet {
        return petRemoteDataSource.getCat()
    }

    fun getDog(): Pet {
        return petLocalDataSource.getDog()
    }
}

 

그리고 viewModel을 아래와 같이 수정해 줍니다.

@HiltViewModel
class PetViewModel @Inject constructor(
    private val petRepository: PetRepository
): ViewModel() {

    private val _pet = MutableLiveData<Pet>()
    val pet: LiveData<Pet> get() = _pet

    fun onCatClicked() {
        setCatName()
    }

    fun onDogClicked() {
        setDogName()
    }

    private fun setCatName() {
        _pet.postValue(petRepository.getCat())
    }

    private fun setDogName() {
        _pet.postValue(petRepository.getDog())
    }
}

이전에 data source에서 함수 호출하던 코드를 repository의 함수를 호출하도록 수정했습니다.

 

위에서 구현한 소스가 저장소 패턴으로 설계된 가장 기본적인 형태라고 생각합니다.

 

최대한 간단하게 구현하기 위해 실제 서버 통신이나 로컬 데이터베이스를 사용하지 않았습니다.

여기에 여러 가지 요소가 추가될 수 있겠지만, 오로지 핵심만을 표현하기 위해 모두 뺀 채 view model, repository, data source 클래스만 작성하였습니다.

 

ViewModel에서 Repository 함수를 호출하고, Repository는 상황이나 조건에 맞춰 remote(서버) 혹은 local(로컬 데이터베이스)의 데이터를 호출합니다.

이때, 데이터를 받아오는 코드를 repository에 바로 작성하는 것이 아니라 data source를 나누어 해당 데이터 요청을 관리합니다.

 

이것이 repository pattern의 핵심이라 생각할 수 있습니다.

 

에뮬레이터에서 실행해 보겠습니다.

figure1. run

 

문제없이 refactoring 되었습니다.

 

이상 포스팅을 마치겠습니다.

 

감사합니다.

728x90

댓글