본문 바로가기
안드로이드 자바

[JAVA][Android] MVVM 패턴을 이용하여 간단한 간식내기 어플 만들기

by teamnova 2021. 10. 22.

안녕하세요. 이번 시간에는 MVVM 패턴을 익히기 위해서 버튼을 클릭해 바뀐 데이터가 화면에 출력되는 예제를 만들어 보도록 하겠습니다.

 

MVVM 패턴의 역사

MVVM( Model View ViewModel )은 Microsoft 설계자 인 Cooper & Peters에 의해 탄생된 디자인 패턴입니다.  그리고  John Gossman에 의해 2005년 발표 되어 클라이언트 기반의 플랫폼에서 조금씩 사용되기 시작했습니다.

 

 

MVVM 패턴 구조

MVVM 패턴 구조 (출처 : 위키피디아)

View : UI 요소를 표시하며 사용자가 발생한 이벤트를 받는 역할을 합니다.

 

ViewModel : UI 요소에 들어갈 데이터를 관리합니다. 그리고 Model과 View 사이의 다리 역할을 합니다. 

 

Model : 데이터 전반적인 것을 처리하는 역할을 합니다.

 

MVVM 패턴의 장점

여러 화면이 있더라도 비슷한 데이터를 가지고 있는 애라면 같은 ViewModel을 공유할 수 있다.

(MVP 처럼 1:1이 아니기 때문)

ViewModel이 직접적으로 요소을 그리라고 View에게 요청하지 않기 때문

 

MVVM 패턴의 단점

간단한 프로젝트에 사용하기에는 과하다.

비교적 구현 구조가 복잡하고 설계가 수비지 않다.

 

MVVM 패턴 구현

이번 예제의 프로젝트 구조입니다.

프로젝트 구조

XML 화면

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="viewModel"
            type="com.example.snacker.viewmodel.ViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".view.MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/main_info"
            android:textSize="40sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.25" />


        <TextView
            android:id="@+id/user_textview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="30sp"
            android:text="@{viewModel.winner}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.4" />

        <Button
            android:id="@+id/ok_btnview"
            android:layout_width="114dp"
            android:layout_height="68dp"
            android:text="@string/main_btn"
            android:textSize="20sp"
            android:textStyle="bold"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.7" />


    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

View

MainActivity

public class MainActivity extends AppCompatActivity {


    ActivityMainBinding binding; // 상속 ViewDataBinding
    ViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Logger.d("Main_onCrete() 실행");
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main); // Activity Content's View - Layout 연결 & 반환 : ViewDataBinding을 상속하는 제네릭 타입

        viewModel = new ViewModel(Database.getInstance());
        binding.setViewModel(viewModel);

        binding.okBtnview.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Logger.d("버튼 클릭");
                viewModel.getUser();
            }
        });
    }
}

ViewModel

public class ViewModel extends BaseObservable {
    private Database database;
    private List<Person> items = new ArrayList<>();
    private String winner;

    public ViewModel(Database database){
        Logger.d("ViewModel 생성자 실행 | DB(Model) 참조");
        this.database = database;

        this.database.setOnDatabaseListener(new Database.DatabaseListener() {
            @Override
            public void onChanged() {
                Logger.d("리스너 실행");
                winner = null;
                winner = database.getWinner();
                notifyChange();
            }
        });
    }

    public void getUser() {
        Logger.d("db에게 user(winner)를 달라고 요청");
        database.getUser();
    }

    public String getWinner(){
        Logger.d("Winner 반환 (%s)", winner);
        return winner;
    }
}

Model

Database.java

public class Database {
    private static Database instance;
    private ArrayList<Person> personList = new ArrayList<>();
    private String winner;
    private DatabaseListener databaseListener;

    private Database(){
        Logger.d("Model인 Database 생성");
        personList.add(new Person(0, "최OO"));
        personList.add(new Person(1, "김OO"));
        personList.add(new Person(2, "고OO"));
        personList.add(new Person(3, "문OO"));
        personList.add(new Person(4, "윤OO"));
    }

    public static Database getInstance() {
        Logger.d("Model에 접근 할 수 있도록 DB 인스턴스 값 요청");
        if (instance == null) {
            instance = new Database();
        }
        return instance;
    }

    public void getUser() {
        Logger.d("당첨자 획득");
        winner = personList.get((int)(Math.random()*5)).getName();
        notifyChange();
    }
    private void notifyChange() {
        if (databaseListener != null) {
            Logger.d("Model | Data 변경 되어 notify 하라고 알림");
            databaseListener.onChanged();
        }
    }

    public void setOnDatabaseListener(DatabaseListener databaseListener) {
        Logger.d("DatabaseListener 구현 객체 참조 변수 세팅 (arg1 : %s)",databaseListener.getClass().getSimpleName());
        this.databaseListener = databaseListener;
    }

    public String getWinner(){
        return winner;
    }

    public interface DatabaseListener {
        void onChanged();
    }
}

Person.java

public class Person {
    private long id;
    private String name;

    public Person(long id, String name) {
        this.id = id;
        this.name = name;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 

안에 있는 Logger 라이브러리는 아래 스틱코드를 이용해서 간단하게 구현이 가능합니다.

https://stickode.com/detail.html?no=2519 

 

스틱코드

 

stickode.com