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

[JAVA][Android] ViewModel, LiveData 활용하기

by teamnova 2021. 7. 19.

오늘은 모던 안드로이드인 Jetpack 의 구성요소인 ViewModel과 LiveData를 알아보겠습니다.

 

 

먼저 안드로이드 Jetpack이 무엇일까요?

 

 

Jetpack은 개발자가 관심 있는 코드에 집중할 수 있도록 권장사항 준수, 상용구 코드 제거, 모든 Android 버전과 기기에서 일관되게 작동하는 코드 작성을 돕는 라이브러리 모음입니다.

 

안드로이드 Jetpack은 개발에 자주 쓰이는 여러 라이브러리들과 툴들을 묶어놓은 모음집입니다. 

 

 

ViewModel은 View로부터 독립적이며, View가 필요로 하는 데이터만을 소요합니다.

액티비티, 프래그먼트 생명주기에 종속되지 않게 하기 위한 특징이 있습니다.

 

액티비티 생명주기는 다들 아시죠?

 

 

 

 

그럼 LiveData는 또 뭘까요?

 

식별 가능한 데이터 홀더 클래스 입니다. 

 

일반 식별 가능한 클래스와 달리 LiveData는 생명 주기를 인식합니다.  즉, 액티비티, 프래그먼트, 서비스 등 다른 앱 구성요소의 생명 주기를 고려합니다.

 

 

그럼 이 둘을 활용하여 간단하게 내가 입력한 데이터를 화면에 표시하는 앱을 구현해보겠습니다.

 

스틱코드 즐겨찾기는 하셨죠?

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

 

스틱코드

 

stickode.com

 

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

    <EditText
        android:id="@+id/inputName"
        android:layout_width="130dp"
        android:layout_height="44dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/inputButton"
        app:layout_constraintHorizontal_bias="0.849"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.43" />

    <Button
        android:id="@+id/inputButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="76dp"
        android:text="입력"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.433" />

    <TextView
        android:id="@+id/nameView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="입력한 내용"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.295"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

 

먼저 ViewModel을 작성합니다.

 

NameViewModel.java

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

public class NameViewModel extends ViewModel {
    private MutableLiveData<String> currentName;
    public MutableLiveData<String> getCurrentName() {
        if (currentName == null) {
            currentName = new MutableLiveData<String>();
        }
        return currentName;
    }
}

 

MutableLiveData는 데이터를 감싸기 위한 wrapper클래스이다. 

getCurrentName()을 통해 currentName 필드에 접근하도록 한다.

setValue(), postValue() 같은 메서드를 통해 값을 변화 시킬수 있습니다.

 

 

MainActivity.java

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    EditText nameInputView;
    TextView nameTextView;
    Button inputButton;

    NameViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //ui 컴포넌트 바인딩
        nameInputView = findViewById(R.id.inputName);
        nameTextView = findViewById(R.id.nameView);
        inputButton = findViewById(R.id.inputButton);

        //뷰모델 객체 생성
        viewModel = new ViewModelProvider(this, new NameViewModelFactory())
                .get(NameViewModel.class);

        //옵저버 정의 - 데이터가 변하는 이벤트 발생시 처리할 핸들러(람다)
        Observer<String> nameObserver = newName -> nameTextView.setText(newName);

        //뷰모델에 옵저버 등록
        viewModel.getCurrentName().observe(this, nameObserver);

        //ui 이벤트 처리 - 뷰모델 이용
        inputButton.setOnClickListener(view -> {
            String inputName = nameInputView.getText().toString();
            viewModel.getCurrentName().setValue(inputName);
        });
    }
}

 

onCreate 메서드에 ViewModel 객체를 생성하고

Observer를 정의한 뒤, ViewModel 객체에서 지켜볼 데이터를 등록합니다. 

 

ViewModel 객체를 생성할 때, 두번째 파라미터에 Factory를 넘겨줘야 합니다. 

 

그럼 Factory를 구현하겠습니다.

 

 

NameViewModelFactory.java

import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;

public class NameViewModelFactory implements ViewModelProvider.Factory{
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        try {
            return modelClass.newInstance();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("Factory Runtime Error");
        } catch (InstantiationException e) {
            e.printStackTrace();
            throw new RuntimeException("Runtime Error");
        }
    }
}

 

 

수행할 로직을 MainActivity에 작성해 줘야겠죠?

 

 

MainActivity.java

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    EditText nameInputView;
    TextView nameTextView;
    Button inputButton;

    NameViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //ui 컴포넌트 바인딩
        nameInputView = findViewById(R.id.inputName);
        nameTextView = findViewById(R.id.nameView);
        inputButton = findViewById(R.id.inputButton);

        //뷰모델 객체 생성
        viewModel = new ViewModelProvider(this, new NameViewModelFactory())
                .get(NameViewModel.class);

        //옵저버 정의 - 데이터가 변하는 이벤트 발생시 처리할 핸들러(람다)
        Observer<String> nameObserver = newName -> nameTextView.setText(newName);

        //뷰모델에 옵저버 등록
        viewModel.getCurrentName().observe(this, nameObserver);

        //ui 이벤트 처리 - 뷰모델 이용
        inputButton.setOnClickListener(view -> {
            String inputName = nameInputView.getText().toString();
            viewModel.getCurrentName().setValue(inputName);
        });
    }
}