DataBinding을 이용해 앱을 만들 때 같이 사용되는 것으로 LiveData와 ViewModel이 있다는 것을 알게 되었다.
MVVM 구조를 구현하기 위해 사용된다는데 MVVM 패턴에 대한 자세한 내용은 나중에 다루도록 하고 일단은 LiveData와 ViewModel에 대해 알아보았다.
LiveData는 이전 글에서 사용한 Observable 자료형과 비슷하다.
둘 다 데이터를 관찰하다 변경사항이 생기면 이를 알려준다.
둘의 차이점은 LiveData는 수명주기를 인식한다는 것이다.
LiveData는 수명주기를 알고 있기에 UI가 동작하지 않는다면 자동으로 동작하지 않는다.
하지만 Observable 자료형은 수명주기를 인식하지 않기 때문에 UI가 종료된 후에도 동작하여 제어해주지 않으면 리소스를 낭비하게 된다.
이러한 특징 때문에 Observable 자료형보단 LiveData의 사용이 더 추천된다.
이전 글에서 Observable 자료형을 사용한것에 공부가 부족했다고 느꼈다.
후술할 ViewModel에서 이 LiveData 객체를 사용해보겠다.
지금까지는 앱을 만들 때 구조를 생각하지 않고 액티비티나 프래그먼트에만 코드를 잔뜩 작성하여 만들어왔다. 하지만 찾아보니 유지보수 등을 위해 권장대는 구조들이 있었다. 위에서 언급한 MVVM 구조가 그런 것이다.
이러한 구조를 구현하기 위해 DataBinding과 같은 것들이 탄생하게 되었고 ViewModel도 그 중 하나이다.
ViewModel은 다음과 같은 수명주기를 갖는다.

액티비티와 프래그먼트와는 다르게 긴 수명주기를 가지고 있다.
이를 통헤 액티비티/프래그먼트의 생명주기에 영향 받지 않고 데이터를 유지할 수 있다.
또한 ViewModel에서 데이터를 관리함에 따라 액티비티/프래그먼트와 데이터를 분리할 수 있다.
다음은 DataBinding과 LiveData, ViewModel을 사용해본 예시이다.
먼저 DataBinding을 사용하기 위해 build.gradle 에 dataBinding을 추가하였다.
android {
...
buildFeatures {
dataBinding = true
}
}
ViewModel을 상속받아 만든 클래스 SampleViewModel을 만들었다.
public class SampleViewModel extends ViewModel {
private MutableLiveData<String> _text = new MutableLiveData<>();
LiveData<String> text = _text;
private MutableLiveData<String> _button = new MutableLiveData<>();
LiveData<String> button = _button;
public void onButtonClicked(View view) {
_button.setValue(_button.getValue() + "+");
}
public void onTextChanged(CharSequence s, int start, int before, int count){
_text.setValue(s.toString());
}
}
MutableLiveData 객체들과 LiveData 객체들을 만들고 두 객체를 연결하였다.
private MutableLiveData<String> _text = new MutableLiveData<>();
LiveData<String> text = _text;
private MutableLiveData<String> _button = new MutableLiveData<>();
LiveData<String> button = _button;
MutableLiveData는 LiveData를 상속받는 클래스로 값의 수정이 가능한 LiveData 객체이다.
MutableLiveData와 LiveData를 연결한 후
ViewModel 안에선 MutableLiveData 객체를 변경하여 값을 변경하고,
액티비티/프래그먼트에선 LiveData를 관찰해 값이 변경됨을 알게 된다.
이렇게 만듦으로써 액티비티/프래그먼트에선 값을 수정하지 못하게 만든다.
그 후 DataBinding을 통해 레이아웃에서 사용할 함수 두 개를 만들었다.
public void onButtonClicked(View view) {
_button.setValue(_button.getValue() + "+");
}
public void onTextChanged(CharSequence s, int start, int before, int count){
_text.setValue(s.toString());
}
레이아웃은 EditText, TextView, Button 으로 구성하였다.

DataBinding을 통해 바인딩할 변수 data에 위에서 만든 ViewHolder 클래스를 지정해주었고
EditText와 Button의 콜백 메소드에 위의 ViewHolder에서 만든 메소드를 사용하였다.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="data"
type="com.example.viewmodel_livedata.SampleViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
... >
<EditText
...
android:onTextChanged="@{data::onTextChanged}"
... />
<TextView
... />
<Button
...
android:onClick="@{data::onButtonClicked}"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
이로인해 UI의 Button이 눌리면 ViewHolder의 button 객체에 +문자가 추가되고,
UI의 EditText가 변화하면 ViewHolder의 text 객체가 변하게된다.
마지막으로 MainActivity에 코드를 작성하였다.
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private SampleViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
//setContentView(R.layout.activity_main);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setLifecycleOwner(this);
viewModel = new ViewModelProvider(this).get(SampleViewModel.class);
binding.setData(viewModel);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
viewModel.button.observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
binding.button.setText(s);
}
});
viewModel.text.observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
binding.textView.setText(s);
}
});
}
}
DataBinding을 적용하였고, ViewModel을 초기화하였다.
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private SampleViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
//setContentView(R.layout.activity_main);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setLifecycleOwner(this);
viewModel = new ViewModelProvider(this).get(SampleViewModel.class);
binding.setData(viewModel);
...
}
다음으로 observe를 사용해 ViewModel안의 LiveData를 관찰하고, 변화가 있을때 UI를 갱신하는 코드를 작성하였다.
viewModel.button.observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
binding.button.setText(s);
}
});
viewModel.text.observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
binding.textView.setText(s);
}
});
ViewModel의 button 객체가 변하면 Button UI의 text가 변하도록 하였고,
ViewModel의 text 객체가 변하면 TextView UI의 text가 변하도록 하였다.
앱을 실행하여 Button을 누르면 버튼의 text가 변하고,
EditText의 내용을 수정하면 TextView의 내용이 그에따라 변한다.

ViewModel 클래스를 초기화할 때 그 안의 MutableLiveData 객체를 ViewModel안에서 초기화할 수 있다. 하지만 액티비티/프래그먼트에선 ViewModel안의 객체에 손댈 수 없다.
하지만 ViewModel 안에서만 객체를 초기화시킬 수 있다면 상황에 따라 주어지는 데이터가 다른 상황에서 ViewModel을 사용할 수 없을꺼라 생각했다.
예를 들어 다른 액티비티에서 Intent를 통해 데이터를 받아 그 데이터를 화면에 보여줘야 하는 상황이 생긴다면 이는 ViewModel 안에서 적용시킬 수 없다.
그렇기에 액티비티/프래그먼트에서 매개변수를 받아 ViewModel을 초기화하는 방법을 알아보았다.
먼저 ViewModel 클래스에 다음과 같은 생성자를 만들었다.
public class SampleViewModel extends ViewModel {
private MutableLiveData<String> _text = new MutableLiveData<>();
LiveData<String> text = _text;
private MutableLiveData<String> _button = new MutableLiveData<>();
LiveData<String> button = _button;
public SampleViewModel(String text, String button) {
_text.setValue(text);
_button.setValue(button);
}
...
}
두 String 문자열을 받아 LiveData인 text와 button를 설정해준다.
다음으로 ViewModel을 위한 Factory 클래스가 필요하다.
ViewHolderProvider.Factory를 상속받은 클래스 SampleViewModelFactory 클래스를 다음과 같이 만들었다.
public class SampleViewModelFactory implements ViewModelProvider.Factory {
private String mText;
private String mButton;
public SampleViewModelFactory(String mText, String mButton) {
this.mText = mText;
this.mButton = mButton;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new SampleViewModel(mText, mButton);
}
}
MainActivity에서 ViewHolder 클래스를 초기화하는 코드를 다음처럼 바꿔주었다.
//viewModel = new ViewModelProvider(this).get(SampleViewModel.class);
viewModel = new ViewModelProvider(this, new SampleViewModelFactory("InitText", "InitButton")).get(SampleViewModel.class);
이것으로 앱을 실행하면 ViewHolder 클래스를 초기화할 때 SampleViewModelFactory에 매개변수로 넣어준 값으로 LiveData가 초기화된다.

참고 사이트