진행중인 프로젝트에 ViewBinding과 DataBinding을 적용하도록 수정하여 MVVM 패턴을 적용해보려고 해보았다.
먼저 build.gradle에 dataBinding과 viewBinding을 추가하였다.
android {
...
buildFeatures{
viewBinding = true
dataBinding = true
}
}
MainActivity.java에 ViewBinding을 적용하였다.
public class MainActivity extends AppCompatActivity implements onItemSwipeListener {
...
private ActivityMainBinding mainBinding;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// View Binding
mainBinding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(mainBinding.getRoot());
...
// floatingButton goto AddActivity
mainBinding.floatingActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(getApplicationContext(), AddActivity.class);
makeResultLauncher.launch(intent);
}
});
...
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mainBinding.recyclerView.setLayoutManager(layoutManager);
mainBinding.recyclerView.setAdapter(adapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new TaskItemTouchHelperCallback(this));
itemTouchHelper.attachToRecyclerView(mainBinding.recyclerView);
loadTaskListData();
}
MainActivity에서 사용하는 View는 RecyclerView와 FloatingButton 둘 밖에 없으므로 DataBinding을 사용할 필요를 느끼지 못했다. 그래서 ViewBinding만 적용시켰다.
AddActivity에 DataBinding을 적용하였다.
우선 AddActivity에서 MainActivity로 액티비티를 전환하는 이벤트에 사용할 Event Wrapper를 만들었다.
package com.example.codingtest_reviewe;
public class Event<T> {
private T content;
private boolean hasBeenHandled = false;
public Event(T content){
this.content = content;
}
public T getContentIfNotHandled() {
if(hasBeenHandled)
return null;
else{
hasBeenHandled = true;
return content;
}
}
public T peekContent() {
return content;
}
}
다음으로 DataBinding에 사용할 AddViewModel을 만들었다.
LiveData로 제목/년도/날짜/주소 를 저장하는 객체들을 만들었다.
메소드로는 EditText의 onTextChanged에 사용할 제목/주소를 저장하는 메소드와
DatePicker를 띄우는 메소드,
입력값을 확인하고 MainActivity로 액티비티를 전환하는 메소드,
Toast 메세지를 띄우도록 하는 메소드를 만들었다.
public class AddViewModel extends ViewModel {
private MutableLiveData<String> title = new MutableLiveData<>();
public LiveData<String> getTitle() {
return title;
}
private MutableLiveData<String> yearString = new MutableLiveData<>();
public LiveData<String> getYearString() {
return yearString;
}
private MutableLiveData<String> dateString = new MutableLiveData<>();
public LiveData<String> getDateString() {
return dateString;
}
private MutableLiveData<String> address = new MutableLiveData<>();
public LiveData<String> getAddress() {
return address;
}
private MutableLiveData<String> toastMessage = new MutableLiveData<>();
public LiveData<String> getToastMessage() {
return toastMessage;
}
private MutableLiveData<Event<String>> addTaskEvent = new MutableLiveData<>();
public LiveData<Event<String>> getAddTaskEvent() {
return addTaskEvent;
}
public void setTitle(CharSequence s, int start, int before, int count) {
title.setValue(s.toString());
}
public void setAddress(CharSequence s, int start, int before, int count){
address.setValue(s.toString());
}
public void showDatePicker(Context context){
DatePickerDialog.OnDateSetListener dateSetListener = new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker datePicker, int year, int month, int day) {
month = month + 1;
yearString.setValue(Integer.toString(year));
dateString.setValue(month + "/" + day);
}
};
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int day = calendar.get(Calendar.DAY_OF_MONTH);
int style = AlertDialog.THEME_HOLO_LIGHT;
DatePickerDialog datePickerDialog = new DatePickerDialog(context, style, dateSetListener, year, month, day);
datePickerDialog.show();
}
public void onButtonClicked(){
if(title.getValue() == null || title.getValue().isEmpty()) {
setToast("제목을 입력해주세요");
} else if(yearString.getValue() == null || yearString.getValue().isEmpty() || dateString.getValue() == null || dateString.getValue().isEmpty()){
setToast("날짜를 입력해주세요");
} else if(address.getValue() == null || address.getValue().isEmpty()){
setToast("주소를 입력해주세요");
} else {
addTaskEvent.setValue(new Event<String>("add event"));
}
}
public void setToast(String s) {
toastMessage.setValue(s);
}
}
activity_add.xml 레이아웃 파일에 DataBinding을 적용하였다.
<?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="viewModel"
type="com.example.codingtest_reviewe.AddViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="40dp">
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ADD TASK"
android:textSize="34sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/editTextTask_add"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:background="@drawable/input_drawable"
android:ems="10"
android:hint="Task"
android:inputType="text"
android:paddingLeft="10dp"
android:paddingTop="20dp"
android:paddingRight="10dp"
android:paddingBottom="20dp"
android:text="@{viewModel.title}"
android:onTextChanged="@{viewModel.setTitle}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
<Button
android:id="@+id/buttonAdd"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:text="ADD"
android:textSize="24sp"
android:onClick="@{() -> viewModel.onButtonClicked()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/editTextTask_add">
<Button
android:id="@+id/buttonSetTime_add"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:backgroundTint="#00000000"
android:onClick="@{() -> viewModel.showDatePicker(context)}"
android:ems="10"
android:inputType="text" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/input_drawable"
android:orientation="horizontal"
android:paddingLeft="10dp"
android:paddingTop="20dp"
android:paddingRight="10dp"
android:paddingBottom="20dp">
<TextView
android:id="@+id/textYear_add"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:hint="Date"
android:textSize="18sp"
android:text="@{viewModel.yearString}"/>
<TextView
android:id="@+id/textDate_add"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:paddingLeft="20dp"
android:textSize="18sp"
android:text="@{viewModel.dateString}"/>
</LinearLayout>
</FrameLayout>
<EditText
android:id="@+id/editTextAdd_add"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:background="@drawable/input_drawable"
android:ems="10"
android:hint="Address"
android:inputType="text"
android:paddingLeft="10dp"
android:paddingTop="20dp"
android:paddingRight="10dp"
android:paddingBottom="20dp"
android:text="@{viewModel.address}"
android:onTextChanged="@{viewModel.setAddress}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/frameLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
마지막으로 AddActivity.java 파일의 코드를 수정하였다.
public class AddActivity extends AppCompatActivity {
private ActivityAddBinding addBinding;
private AddViewModel addViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Data binding
addBinding = ActivityAddBinding.inflate(getLayoutInflater());
setContentView(addBinding.getRoot());
addViewModel = new ViewModelProvider(this).get(AddViewModel.class);
addBinding.setViewModel(addViewModel);
addBinding.setLifecycleOwner(this);
addViewModel.getToastMessage().observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
Toast.makeText(getApplicationContext(),s , Toast.LENGTH_SHORT).show();
}
});
addViewModel.getAddTaskEvent().observe(this, new Observer<Event<String>>() {
@Override
public void onChanged(Event<String> event) {
if(event != null && event.getContentIfNotHandled() != null){
Intent intent = new Intent();
intent.putExtra("task", addViewModel.getTitle().getValue());
intent.putExtra("date", addViewModel.getYearString().getValue() + " " + addViewModel.getDateString().getValue());
intent.putExtra("address", addViewModel.getAddress().getValue());
setResult(RESULT_OK, intent);
finish();
}
}
});
}
}
getToastMessage()를 observe 하여 Toast 메세지를 띄우고,
getAddTaskEvent()를 observe 하여 Intent를 통해 액티비티를 전환하도록 만들었다.