[안드로이드] MVVM 패턴

J·2023년 3월 20일
1

안드로이드

목록 보기
26/29

1. MVVM 패턴 구조

  • View : UI 요소를 표시하며 사용자가 발생한 이벤트를 받는 역할을 합니다.
  • ViewModel : UI 요소에 들어갈 데이터를 관리합니다. 그리고 Model과 View 사이의 다리 역할을 합니다.
  • Model : 데이터 전반적인 것을 처리하는 역할을 합니다.

2. MVVM 패턴의 장점

여러 화면이 있더라도 비슷한 데이터를 가지고 있는 애라면 같은 ViewModel을 공유할 수 있다.
(MVP 처럼 1:1이 아니기 때문)
ViewModel이 직접적으로 요소를 그리라고 View에게 요청하지 않기 때문.

3. MVVM 패턴의 단점

간단한 프로젝트에 사용하기에는 과하다.
비교적 구현 구조가 복잡하고 설계가 수비지 않다.

4. MVVM 패턴 구현

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

a. build.gradle (모듈 앱)

dependencies {
    implementation 'androidx.appcompat:appcompat:1.5.1'
    implementation 'com.google.android.material:material:1.7.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'androidx.drawerlayout:drawerlayout:1.1.1'
    implementation 'com.orhanobut:logger:2.2.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.4'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
}

b. activity_main.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.project1.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="오늘의 간식 당번"
            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:text="@{viewModel.winner}"
            android:textSize="30sp"
            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="돌리기"
            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="1.0" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

c. MainActivity.java

package com.example.project1.view;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import android.os.Bundle;
import android.view.View;

import com.example.project1.R;
import com.example.project1.databinding.ActivityMainBinding;
import com.example.project1.model.Database;
import com.example.project1.viewmodel.ViewModel;
import com.orhanobut.logger.Logger;

public class MainActivity extends AppCompatActivity {

    ActivityMainBinding binding; //상속 ViewDataBinding
    ViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Logger.d("Main_onCreate() 실행");
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

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

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

    }
}

d. ViewModel.java

package com.example.project1.viewmodel;

import androidx.databinding.BaseObservable;

import com.example.project1.model.Database;
import com.example.project1.model.Person;
import com.orhanobut.logger.Logger;

import java.util.ArrayList;
import java.util.List;

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;
    }
}

e. Database

package com.example.project1.model;

import com.orhanobut.logger.Logger;

import java.util.ArrayList;

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, "최00"));
        personList.add(new Person(1, "김00"));
        personList.add(new Person(2, "고00"));
        personList.add(new Person(3, "문00"));
        personList.add(new Person(4, "윤00"));
    }

    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();
    }
}

f. Person

package com.example.project1.model;

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;
    }

}

0개의 댓글