Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

ViewModel 개요  Android Jetpack의 일부

ViewModel 클래스는 수명 주기를 고려하여 UI 관련 데이터를 저장하고 관리하도록 설계되었습니다. ViewModel 클래스를 사용하면 화면 회전과 같이 구성을 변경할 때도 데이터를 유지할 수 있습니다.

Android 프레임워크는 활동 및 프래그먼트와 같은 UI 컨트롤러의 수명 주기를 관리합니다. 프레임워크는 특정 사용자 작업이나 완전히 통제할 수 없는 기기 이벤트에 대한 응답으로 UI 컨트롤러를 제거하거나 다시 만들도록 결정할 수 있습니다.

시스템에서 UI 컨트롤러를 제거하거나 다시 만들면 컨트롤러에 저장된 일시적인 모든 UI 관련 데이터가 손실됩니다. 예를 들어 앱은 활동 중 하나에 사용자 목록을 포함할 수 있습니다. 구성 변경을 위해 활동을 다시 생성하면 새 활동은 사용자 목록을 다시 가져와야 합니다. 데이터가 단순한 경우 활동은 onSaveInstanceState() 메서드를 사용하여 onCreate()의 번들에서 데이터를 복원할 수 있습니다. 하지만 이 접근 방법은 사용자 목록이나 비트맵과 같은 대용량일 가능성이 높은 데이터가 아니라, 직렬화했다가 다시 역직렬화할 수 있는 소량의 데이터에만 적합합니다.

또 다른 문제는 UI 컨트롤러가 반환하는 데 시간이 걸릴 수 있는 비동기 호출을 자주 해야 한다는 점입니다. UI 컨트롤러는 이러한 비동기 호출을 관리해야 하며, 메모리 누출 가능성을 방지하기 위해 시스템에서 호출 폐기 후 호출을 정리하는지 확인해야 합니다. 이러한 관리에는 많은 유지보수가 필요하며, 구성 변경 시 개체가 다시 생성되는 경우 개체가 이미 실행된 호출을 다시 해야 할 수 있으므로 리소스가 낭비됩니다.

활동 및 프래그먼트와 같은 UI 컨트롤러는 주로 UI 데이터를 표시하거나, 사용자 작업에 반응하거나, 권한 요청과 같은 운영체제 커뮤니케이션을 처리하기 위한 것입니다. 또한 UI 컨트롤러에 데이터베이스나 네트워크에서 데이터 로드를 담당하도록 요구하면 클래스가 팽창됩니다. UI 컨트롤러에 과도한 책임을 할당하면 단일 클래스가 다른 클래스에 작업을 위임하지 않고 홀로 모든 앱 작업을 처리하려고 할 수 있습니다. 또한 이런 방법으로 UI 컨트롤러에 과도한 책임을 할당하면 테스트가 훨씬 더 어려워집니다.

UI 컨트롤러 로직에서 뷰 데이터 소유권을 분리하는 방법이 훨씬 더 쉽고 효율적입니다.

ViewModel 구현

아키텍처 구성요소는 UI의 데이터 준비를 담당하는 UI 컨트롤러에 ViewModel 도우미 클래스를 제공합니다. ViewModel 객체는 구성이 변경되는 동안 자동으로 보관되므로, 이러한 객체가 보유한 데이터는 다음 활동 또는 프래그먼트 인스턴스에서 즉시 사용할 수 있습니다. 예를 들어 앱에서 사용자 목록을 표시해야 한다면 다음 샘플 코드에 설명된 대로 사용자 목록을 확보하여 활동이나 프래그먼트 대신 ViewModel에 보관하도록 책임을 할당해야 합니다.

Kotlin

    class MyViewModel : ViewModel() {
        private val users: MutableLiveData<List<User>> by lazy {
            MutableLiveData().also {
                loadUsers()
            }
        }

        fun getUsers(): LiveData<List<User>> {
            return users
        }

        private fun loadUsers() {
            // Do an asynchronous operation to fetch users.
        }
    }
    

자바

    public class MyViewModel extends ViewModel {
        private MutableLiveData<List<User>> users;
        public LiveData<List<User>> getUsers() {
            if (users == null) {
                users = new MutableLiveData<List<User>>();
                loadUsers();
            }
            return users;
        }

        private void loadUsers() {
            // Do an asynchronous operation to fetch users.
        }
    }
    

그 후에 다음과 같이 활동에서 목록에 액세스할 수 있습니다.

Kotlin

    class MyActivity : AppCompatActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
            // Create a ViewModel the first time the system calls an activity's onCreate() method.
            // Re-created activities receive the same MyViewModel instance created by the first activity.

            // Use the 'by viewModels()' Kotlin property delegate
            // from the activity-ktx artifact
            val model: MyViewModel by viewModels()
            model.getUsers().observe(this, Observer<List<User>>{ users ->
                // update UI
            })
        }
    }
    

자바

    public class MyActivity extends AppCompatActivity {
        public void onCreate(Bundle savedInstanceState) {
            // Create a ViewModel the first time the system calls an activity's onCreate() method.
            // Re-created activities receive the same MyViewModel instance created by the first activity.

            MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
            model.getUsers().observe(this, users -> {
                // update UI
            });
        }
    }
    

활동이 다시 생성되면 첫 번째 활동에서 생성된 동일한 MyViewModel 인스턴스를 받습니다. 소유자 활동이 완료되면 프레임워크는 리소스를 정리할 수 있도록 ViewModel 객체의 onCleared() 메서드를 호출합니다.

ViewModel 객체는 뷰 또는 LifecycleOwners의 특정 인스턴스화보다 오래 지속되도록 설계되었습니다. 이러한 설계로 인해 뷰 및 Lifecycle 객체에 관해 알지 못할 때도 ViewModel을 다루는 테스트를 더 쉽게 작성할 수 있습니다. ViewModel 객체에는 LiveData 객체와 같은 LifecycleObservers가 포함될 수 있습니다. 그러나 ViewModel 객체는 LiveData 객체와 같이 수명 주기를 인식하는 Observable의 변경사항을 관찰해서는 안 됩니다. 예를 들어 ViewModel은 시스템 서비스를 찾는 데 Application 컨텍스트가 필요하면 AndroidViewModel 클래스를 확장하고 생성자에 Application을 받는 생성자를 포함할 수 있습니다(Application 클래스가 Context를 확장하므로).

ViewModel의 수명 주기

ViewModel 객체의 범위는 ViewModel을 가져올 때 ViewModelProvider에 전달되는 Lifecycle로 지정됩니다. ViewModel은 범위가 지정된 Lifecycle이 영구적으로 경과될 때까지, 즉 활동에서는 활동이 끝날 때까지 그리고 프래그먼트에서는 프래그먼트가 분리될 때까지 메모리에 남아 있습니다.

그림 1에서는 활동이 회전을 거친 후 완료될 때까지 활동의 다양한 수명 주기 상태를 보여줍니다. 또한 관련 활동 수명 주기 옆에 ViewModel의 전체 기간도 보여줍니다. 이 특정 다이어그램에서는 활동의 상태를 보여줍니다. 동일한 기본 상태가 프래그먼트의 수명 주기에 적용됩니다.

활동 상태 변경에 따라 ViewModel의 수명 주기를 설명합니다.

일반적으로 시스템에서 활동 객체의 onCreate() 메서드를 처음 호출할 때 ViewModel을 요청합니다. 시스템은 활동 기간 내내(예: 기기 화면이 회전될 때) onCreate() 메서드를 여러 번 호출할 수 있습니다. ViewModel이 처음 요청되었을 때부터 활동이 끝나고 폐기될 때까지 ViewModel은 존재합니다.

프래그먼트 간 데이터 공유

활동에 속한 둘 이상의 프래그먼트가 서로 커뮤니케이션해야 하는 일은 매우 일반적입니다. 사용자가 목록에서 항목을 선택하는 프래그먼트와 선택된 항목의 콘텐츠를 표시하는 또 다른 프래그먼트가 있는 master-detail 프래그먼트의 일반적인 사례를 가정해보세요. 두 프래그먼트가 모두 인터페이스 설명을 정의해야 하고 소유자 활동이 두 프래그먼트를 함께 결합해야 하므로 이 사례는 간단히 처리할 수 있는 작업이 아닙니다. 또한 두 프래그먼트는 모두 다른 프래그먼트가 아직 생성되지 않았거나 표시되지 않은 시나리오도 처리해야 합니다.

이러한 일반적인 고충은 ViewModel 객체를 사용하면 해결할 수 있습니다. 이러한 프래그먼트는 다음 샘플 코드에서와 같이 이 커뮤니케이션을 처리하기 위한 활동 범위를 사용하여 ViewModel을 공유할 수 있습니다.

Kotlin

    class SharedViewModel : ViewModel() {
        val selected = MutableLiveData<Item>()

        fun select(item: Item) {
            selected.value = item
        }
    }

    class MasterFragment : Fragment() {

        private lateinit var itemSelector: Selector

        // Use the 'by activityViewModels()' Kotlin property delegate
        // from the fragment-ktx artifact
        private val model: SharedViewModel by activityViewModels()

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            itemSelector.setOnClickListener { item ->
                // Update the UI
            }
        }
    }

    class DetailFragment : Fragment() {

        // Use the 'by activityViewModels()' Kotlin property delegate
        // from the fragment-ktx artifact
        private val model: SharedViewModel by activityViewModels()

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
                // Update the UI
            })
        }
    }
    

자바

    public class SharedViewModel extends ViewModel {
        private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

        public void select(Item item) {
            selected.setValue(item);
        }

        public LiveData<Item> getSelected() {
            return selected;
        }
    }

    public class MasterFragment extends Fragment {
        private SharedViewModel model;

        public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
            itemSelector.setOnClickListener(item -> {
                model.select(item);
            });
        }
    }

    public class DetailFragment extends Fragment {

        public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
            model.getSelected().observe(getViewLifecycleOwner(), { item ->
               // Update the UI.
            });
        }
    }
    

두 프래그먼트는 모두 자신이 포함된 활동을 검색합니다. 그러면 각 프래그먼트는 ViewModelProvider를 가져올 때 이 활동으로 범위가 지정된 동일한 SharedViewModel 인스턴스를 받습니다.

이 접근 방법에는 다음과 같은 이점이 있습니다.

  • 활동은 아무것도 하지 않아도 되거나, 이 커뮤니케이션에 관해 어떤 것도 알 필요가 없습니다.
  • 프래그먼트는 SharedViewModel 계약 외에 서로 알 필요가 없습니다. 프래그먼트 중 하나가 사라져도 다른 프래그먼트는 계속 평소대로 작동합니다.
  • 각 프래그먼트는 자체 수명 주기가 있으며, 다른 프래그먼트 수명 주기의 영향을 받지 않습니다. 한 프래그먼트가 다른 프래그먼트를 대체해도, UI는 아무 문제 없이 계속 작동합니다.

ViewModel로 로더 대체하기

CursorLoader와 같은 로더 클래스는 앱 UI의 데이터와 데이터베이스 간의 동기화를 유지하는 데 자주 사용됩니다. ViewModel을 몇 가지 클래스와 함께 사용하여 로더를 대체할 수 있습니다. ViewModel을 사용하면 UI 컨트롤러가 데이터 로드 작업에서 분리됩니다. 즉, 클래스 간에 강력한 참조가 적어집니다.

일반적인 로더 사용 방법 중 하나로, 앱이 CursorLoader를 사용하여 데이터베이스의 내용을 관찰할 수 있습니다. 데이터베이스에서 값이 변경되면 로더가 자동으로 데이터 새로고침을 트리거하고 UI를 업데이트합니다.

그림 2. 로더로 데이터 로드하기

ViewModelRoomLiveData와 함께 작업하여 로더를 대체합니다. ViewModel은 기기 구성이 변경되어도 데이터가 유지되도록 보장합니다. 데이터베이스가 변경되면 Room에서 LiveData에 변경을 알리고, 알림을 받은 LiveData는 수정된 데이터로 UI를 업데이트합니다.

그림 3. ViewModel로 데이터 로드하기

ViewModel과 함께 코루틴 사용

ViewModel에는 Kotlin 코루틴 지원이 포함됩니다. 자세한 내용은 Android 아키텍처 구성요소로 Kotlin 코루틴 사용을 참조하세요.

추가 정보

데이터가 더 복잡해지면 데이터 로드만을 위한 별도의 클래스를 사용하는 것이 좋습니다. ViewModel의 목적은 UI 컨트롤러의 데이터를 캡슐화하여 구성이 변경되어도 데이터를 유지하는 것입니다. 구성 변경 시 데이터를 로드, 유지 및 관리하는 방법에 관한 자세한 내용은 UI 상태 저장을 참조하세요.

Android 앱 아키텍처 가이드에서는 이러한 함수를 처리하는 저장소 클래스를 빌드할 것을 제안합니다.

추가 리소스

ViewModel 클래스에 관한 자세한 내용은 다음 리소스를 참조하세요.

샘플

Codelab

블로그

동영상