AAC ViewModel

KwangYoung Kim·2021년 8월 24일
1

Android

목록 보기
1/4
post-thumbnail

들어가기전에

MVVM 패턴을 이용한 앱을 만들기 위해 알아보던 중 안드로이드에서 많이 사용하는 AAC의 ViewModel을 알게 되었다.
MVVM 내에서 말하는 ViewModel과 AAC의 ViewModel은 완전히 다르지만 MVVM에서의 ViewModel역할을 AAC ViewModel이 할 수 있기 때문에
정리하고 가려고 한다.

ViewModel은?

수명 주기를 고려하여 UI 관련 데이터를 저장하고 관리하도록 설계된 AAC(Android Architecture Components)의 구성 요소

써야하는 이유

Activity 및 Fragment와 같은 UI 컨트롤러는 UI 데이터를 표시하거나, 사용자 작업에 반응하거나, 권한 요청과 같은 운영체제 커뮤니케이션을 처리하는 것이다.
데이터베이스나 네트워크에서 데이터 로드를 UI 컨트롤러에게 책임지도록 요구하면 클래스가 팽창하게 된다.
UI 컨트롤러에게 이처럼 과도한 책임을 할당하게 되면 다른클래스로 작업이 위임되지 않고, 단일 클래스가 앱의 모든 작업을 처리하려고 할 수 있다.

ViewModeld을 사용하게 되면 이러한 UI 컨트롤러에 부담스러울 수 있는 책임을 분담하고 유지보수, 재사용성, 테스트 등을 쉽게 만들어 준다.

특징

Activity가 종료될 때 까지 Fragment가 분리될 때까지 살아남기 때문에 Activity의 화면 회전 같은 상황에 데이터를 유지할 수 있다.
또 ViewModel을 이용하여 Activity에 포함되어 있는 Fragment에서 통신을 할 수도 있다.

  • ViewModel의 생명주기

구현

  • Activity에서의 사용
class MyViewModel : ViewModel() {
    private val users: MutableLiveData<List<User>> by lazy {
        MutableLiveData<List<User>>().also {
            loadUsers()
        }
    }

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

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

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        val model: ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MyViewModel::class.java)
        
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI
        })
    }
}

ViewModel을 생성하기 위해서는 ViewModel Provider 객체가 필요하다. ViewModelProvider를 생성하기 위해 생성자 매개변수로 ViewModelStoreOwner와 ViewModelProvider.Factory가 필요하다.

ComponentActivity가 ViewModelStoreOwner 인터페이스를 구현하고 있기 때문에 ComponentActivity의 서브 클래스인 AppCompatActivity를 사용하고 있다면 별도로 ViewModelStoreOwner를 구현할 필요는 없다.
Fragment 또한 ViewModelStoreOwner를 구현하고 있다. ViewModel의 생명주기를 Fragment와 함께하길 원하는 경우에는 ViewModelProvider에서 생성자 첫번째인자로 ViewModelStoreOwner를 지정할 때 ComponentActivity 서브클래스 대신 Fragment를 넘겨주면 된다.

ViewModel은 ViewModelStore라는 객체에서 관리한다.
그리고 ViewModelStore는 ViewModelStoreOwner에서 만들고 관리한다.
위에서 말한 ComponentActivity와 Fragment가 ViewModelStoreOwner를 구현하고 있기 때문에 ViewModel 객체를 생성할 때 Activity나 Fragment가 필요하고 이러한 Owner따라 ViewModel의 scope가 달라진다.

  • Fragment 간의 통신
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.
        });
    }
}

위와 같이 ViewModel을 이용하여 Fragment끼리 커뮤니케이션이 가능하다.
ViewModelStoreOwner가 Fragment가 있는 Activity를 사용하여 ViewModel 객체를 가져오게 되면 서로 다른 Fragment에서 동일한 ViewModel객체를 가진다.
그래서 ShareViewModel의 경우 Activity의 생명주기를 따르게 되고 Activity의 생명주기가 다하기 전까지 자유롭게 데이터 공유를 할 수 있다.

이렇게 Fragment간의 데이터를 공유한다면

* 활동은 아무것도 할 필요가 없거나 이 커뮤니케이션에 관해 어떤 것도 알 필요가 없습니다.

* 프래그먼트는 SharedViewModel 계약 외에 서로 알 필요가 없습니다. 프래그먼트 중 하나가 사라져도 다른 프래그먼트는 계속 평소대로 작동합니다.

* 각 프래그먼트는 자체 수명 주기가 있으며, 다른 프래그먼트 수명 주기의 영향을 받지 않습니다. 한 프래그먼트가 다른 프래그먼트를 대체해도, UI는 아무 문제 없이 계속 작동합니다.

위와 같은 이점이 있다.

코틀린을 사용한다면 ktx를 통해 'by viewModels()' 또는 'by activityViewModels()'로 좀 더 편하게 ViewModel을 얻어올 수 있다.

ViewModel을 얻어오는 방법

1. Lifecycle Extensions

파라미터가 없는 ViewModel을 얻어올 때 가장 편리한 방법이다.
androidx.lifecycle의 lifecycle-extensions 모듈을 가져와 사용하면 된다.
module 수준의 build.gradle 에 다음과 같이 추가한다.

위에서 얘기한 'by viewModels()'와 동일하다.

dependencies {
    // ...
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
}
noParamViewModel = ViewModelProvider(this).get(NoParamViewModel::class.java)

2. ViewModelProvider.NewInstanceFactory

안드로이드가 기본적으로 제공하는 클래스이며, ViewModelProvider.Factory 인터페이스를 구현하고 있다.
ViewModel의 파라미터가 필요없거나 팩토리를 커스텀할 필요가 없는 경우에 1번 2번이 사용 가능하다.

noParamViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
            .get(NoParamViewModel::class.java)

3. ViewModelProvider.Factory(생성자 파라미터X)

ViewModelProvider.Factory 인터페이스를 직접 구현한다.

class NoParamViewModelFactory : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return if (modelClass.isAssignableFrom(NoParamViewModel::class.java)) {
            NoParamViewModel() as T
        } else {
            throw IllegalArgumentException()
        }
    }
}

4. ViewModelProvider.Factory(생성자 파라미터O)

ViewModel 생성자에 parameter가 있을 시에 Factory 인터페이스를 구현할 때 추가해주면 된다.

class HasParamViewModel(val param: String) : ViewModel()

class HasParamViewModelFactory(private val param: String) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return if (modelClass.isAssignableFrom(HasParamViewModel::class.java)) {
            HasParamViewModel(param) as T
        } else {
            throw IllegalArgumentException()
        }
    }
}

5. AndroidViewModel(application, 생성자 파라미터X)

구글에서는 ViewModel에서 Context 객체를 참조하지 않는 것을 권장하고 있다.
하지만 꼭 필요할 시에는 AndroidViewModel을 사용하면 된다.

class NoParamAndroidViewModel(application: Application) : AndroidViewModel(application)

class MainActivity : AppCompatActivity() {
 
    private lateinit var noParamAndroidViewModel: NoParamAndroidViewModel
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        noParamAndroidViewModel = ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application))
            .get(NoParamAndroidViewModel::class.java)
    }
}

6. AndroidViewModel(application, 생성자 파라미터O)

파라미터가 있는 AndroidViewModel을 얻어오는 방법

class HasParamAndroidViewModel(application: Application, val param: String)
    : AndroidViewModel(application)
    
class HasParamAndroidViewModelFactory(private val application: Application, private val param: String)
    : ViewModelProvider.NewInstanceFactory() {
 
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
            try {
                return modelClass.getConstructor(Application::class.java, String::class.java)
                    .newInstance(application, param)
            } catch (e: NoSuchMethodException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            } catch (e: IllegalAccessException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            } catch (e: InstantiationException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            } catch (e: InvocationTargetException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            }
        }
        return super.create(modelClass)
    }
}

마치며

처음에는 안드로이드 개발자 공식 페이지를 참조하여 다 작성하려고 했지만 역시나 정보가 좀 부족하고 설명이 이해가 안가는 부분이 있어 다른 분들이 쓴 블로그를 참고하여(거의 복사..) 작성하였다.

아무래도 첫 게시글이기 때문에 정리가 잘 안된 부분이 있을 수 있다.
나중에 조금 더 보완하여 수정할 수 있으면 좋을 것 같다.🙋🏻‍♂️

출처 / 참고 -
Android developers
blog - 준비된 개발자
blog - Charlezz

profile
축구왕이 되고 싶은 안드로이드 개발자

0개의 댓글