MVVC모델은 액티비티와 데이터모델이 직접 소통을 하며 뷰를 바꾸어내기 때문에 액티비티마다 뷰 모델을 필요로 한다.
먼저, 뷰에 라이브 데이터를 받을 수 있도록 데이터 바인딩한다.
데이터 바인딩을 하려면 먼저 데이터를 파싱할 모델을 만든다.
- 모델
package com.mary.mvvmex2;
public class Note {
private int id;
private String title;
private String description;
private int priority;
public Note(String title, String description, int priority) {
this.title = title;
this.description = description;
this.priority = priority;
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public int getPriority() {
return priority;
}
}
이 때 반드시 getter이 있어야 데이터모델이 데이터베이스나 Retrofit에서 받아올 수 있다.
그리고 데이터를 바인딩 받을 모델을 만들자.
- 레이아웃
activitiy_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/note_item" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="15dp"
android:src="@drawable/ic_add_white" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
note_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="note"
type="com.mary.mvvmex2.Note" />
</data>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp">
<TextView
android:id="@+id/text_view_priority"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:text="@{String.valueOf(note.priority)}"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<TextView
android:id="@+id/text_view_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:layout_toStartOf="@+id/text_view_priority"
android:ellipsize="end"
android:maxLines="1"
android:text="@{note.title}"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<TextView
android:id="@+id/text_view_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/text_view_title"
android:text="@{note.description}"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
</layout>
text에 보면 @{note. }로 적혀있는 것을 알 수 있다.
이것은 뷰모델과 데이터바인딩 하겠다는 뜻으로 이름만 같으면 데이터가 변수에 맞추어서 들어갈 수 있다(리액트와 유사한 방식).
이것을 위해 앱 수준의 Gradle에서 의존성을 부여한다.
buildTypes {
---
}
dataBinding{
enabled=true
}
}
dependencies {
다음처럼 빌드 타입과 의존성 사이에 데이터 바인딩 상태를 true로 걸어주면, 뷰와 데이터의 바인딩이 완료된다.
그리고 뷰모델을 만든다. 이것은 스프링의 서비스와 유사한 방식이다.
(그냥 같은 것이라 생각하는 것도 좋을듯)
- 뷰모델
NoteViewModel.java
package com.mary.mvvmex2;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import java.util.List;
public class NoteViewModel extends AndroidViewModel {
//ViewModel이 가진 데이터
private LiveData<List<Note>> allNotes;
private NoteRepository noteRepository=new NoteRepository();
public NoteViewModel(@NonNull Application application) {
super(application);
allNotes=noteRepository.findAll();
}
public LiveData<List<Note>> 구독(){
return allNotes;
}
public void 추가(Note note){
noteRepository.save(note);
}
public void 삭제(Note note){
noteRepository.delete(note);
}
/*public LiveData<List<Note>> 전체보기(){
return noteRepository.findAll();
}*/
}
그리고 뷰 모델이 참고할 데이터베이스 연결을 구현한다
- 저장소
NoteRepository.java
package com.mary.mvvmex2;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.util.ArrayList;
import java.util.List;
public class NoteRepository {
//LiveData는 getter만 있음
//MutableLiveData는 setter도 있음
private MutableLiveData<List<Note>> allNotes =new MutableLiveData<>();
public NoteRepository(){
List<Note> notes=new ArrayList<>();
notes.add(new Note("제목", "설명",0));
allNotes.setValue(notes);
}
public LiveData<List<Note>> findAll(){
return allNotes;
};
public void delete(Note note){
List<Note> notes=allNotes.getValue();
notes.remove(note);
allNotes.setValue(notes);
};
public void save(Note note){
List<Note> notes=allNotes.getValue();
notes.add(note);
allNotes.setValue(notes);
};
}
라이브 데이터에는 두 가지 형태가 있다
일반 LiveData와 MutableLiveData가 있는데, 두 가지는 할 수 있는 일의 영역이 다르다.
LiveData의 경우 영속성 보존을 위해 getter(getValue)만 되고 setter(setValue)은 제공되지 않기 때문에 안드로이드 내부에서 제어할 수 없다. 따라서 일반적인 LiveData는 리액티브 서버에 주로 이용한다.
Mutable은 setter도 제공하기 때문에 영속성이 보장되지 않는 대신 앱 내부에서 제어가 가능하다. 따라서, Retrofit 등을 이용하는 경우 Mutable을 이용하는 것이 좋다.
마지막으로 LiveData를 RecyclerView에 넣어줄 어댑터를 구현한다.
- 어댑터
NoteAdapter.java
package com.mary.mvvmex2;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.mary.mvvmex2.databinding.NoteItemBinding;
import java.util.ArrayList;
import java.util.List;
public class NoteAdapter extends RecyclerView.Adapter<NoteAdapter.NoteHolder> {
private List<Note> notes = new ArrayList<>();
class NoteHolder extends RecyclerView.ViewHolder {
private NoteItemBinding noteItemBinding;
public NoteHolder(@NonNull NoteItemBinding noteItemBinding) {
super(noteItemBinding.getRoot());
this.noteItemBinding=noteItemBinding;
}
}
@NonNull
@Override
public NoteHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
NoteItemBinding noteItemBinding= DataBindingUtil.inflate(
LayoutInflater.from(parent.getContext()),
R.layout.note_item,
parent,
false
);
return new NoteHolder(noteItemBinding);
}
@Override
public void onBindViewHolder(@NonNull NoteHolder holder, int position) {
Note currentNote = notes.get(position);
holder.noteItemBinding.setNote(currentNote);
}
@Override
public int getItemCount() {
return notes.size();
}
public void setNotes(List<Note> notes){
this.notes = notes;
notifyDataSetChanged();
}
public Note getNoteAt(int position){
return notes.get(position);
}
}
라이브 데이터를 이용하는 경우는 일반 어댑터와 뷰홀더 바인딩 형식이 조금 다른데, 일반 리사이클러뷰가 Binding을 View view 데이터 형식을 매개변수로 받는데에 비해 LiveData는 LiveData의 형태로 매개변수를 받는다. 따라서, view를 만드는 viewInflater의 형태도 다르다(일반적인 어댑터는 LayoutInflater로 뷰를 정의하지만, 라이브 데이터는 DataBindingUtil의 형태로 뷰를 정의한다.).
'Android' 카테고리의 다른 글
Android Studio, JAVA] AIDL 서비스 바인딩 (0) | 2020.08.18 |
---|---|
Android Studio, JAVA] 파이어베이스 등록하기 (0) | 2020.08.13 |
Android Studio] MVVM 패턴 (0) | 2020.08.05 |
안드로이드 스튜디오, Java] Room 라이브러리를 이용한 내부 데이터베이스 이용하기 (0) | 2020.08.05 |
안드로이드 스튜디오, JAVA ] 서비스 (0) | 2020.08.05 |