이번에는 라이브 데이터만 다루는 예제이다

 

1. MainActivity

package com.monkey.viewmodeltest;

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

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

public class MainActivity extends AppCompatActivity {

    TextView tvNormal;
    Button btnPlus, btnMinus;

    MutableLiveData<Integer> normalCount = new MutableLiveData<Integer>();

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

        tvNormal = findViewById(R.id.tv_normal);
        btnPlus = findViewById(R.id.btn_plus);
        btnMinus = findViewById(R.id.btn_minus);

        normalCount.observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                setCountText();
            }
        });

        btnPlus.setOnClickListener(v-> {
            normalCount.setValue(normalCount.getValue()+1);
        });

        btnMinus.setOnClickListener(v-> {
            normalCount.setValue(normalCount.getValue()-1);
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        setCountText();
    }

    private void setCountText() {
        tvNormal.setText(normalCount.getValue() +"");
    }
}

 

2. 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">

    <Button
        android:id="@+id/btn_minus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="-"
        app:layout_constraintTop_toBottomOf="@id/tv_normal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/btn_plus" />

    <Button
        android:id="@+id/btn_plus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="+"
        app:layout_constraintLeft_toRightOf="@id/btn_minus"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/btn_minus"
        app:layout_constraintBottom_toBottomOf="@id/btn_minus"/>


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="NORMAL"
        android:textColor="@color/purple_500"
        android:textStyle="bold"
        app:layout_constraintLeft_toLeftOf="@id/tv_normal"
        app:layout_constraintRight_toRightOf="@id/tv_normal"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/tv_normal"
        app:layout_constraintVertical_bias="1"/>

    <TextView
        android:id="@+id/tv_normal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        android:textColor="#000"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

 

plus, minus 버튼에서 setText를 해 주지 않아도

값이 변하면 텍스트가 변하게 된다!

여기서 말하는 ViewModel은 MVVM의 ViewModel이 아님!

 

ViewModel은 보통 LiveData랑 같이 써서

두개를 따로 다루는 예제는 못봤다 -_- ;;

 

그러다 오늘 예제를 발견해서 따라함ㅋㅋ

 

차근차근 할 거라 우선 ViewModel만 맛보는 예제를 준비했다.

 

코드 언어는 Java고 ViewBinding이나 DataBinding은 사용하지 않았음.

 

 

1. MainActivity.class

public class MainActivity extends AppCompatActivity {

    TextView tvViewModel, tvNormal;
    Button btnPlus, btnMinus;

    CounterViewModel counterViewModel;
    int normalCount = 0;

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

        counterViewModel = new ViewModelProvider(this).get(CounterViewModel.class);

        tvViewModel = findViewById(R.id.tv_viewmodel);
        tvNormal = findViewById(R.id.tv_normal);
        btnPlus = findViewById(R.id.btn_plus);
        btnMinus = findViewById(R.id.btn_minus);

        btnPlus.setOnClickListener(v-> {
            normalCount++;
            counterViewModel.count++;
            setCountText();
        });

        btnMinus.setOnClickListener(v-> {
            normalCount--;
            counterViewModel.count--;
            setCountText();
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        setCountText();
    }

    private void setCountText() {
        tvViewModel.setText(counterViewModel.count+"");
        tvNormal.setText(normalCount +"");
    }
}

 

2. CounterViewModel

* class를 public으로 하지 않으면 'Cannot create an instance class ...' 라는 RuntimeException이 발생한다.

public class CounterViewModel extends ViewModel {
   int count=0;
}

 

3. activity_main

<?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">

    <Button
        android:id="@+id/btn_minus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="-"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/btn_plus"
        app:layout_constraintTop_toBottomOf="@id/tv_viewmodel"
        app:layout_constraintBottom_toBottomOf="parent"/>

    <Button
        android:id="@+id/btn_plus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="+"
        app:layout_constraintLeft_toRightOf="@id/btn_minus"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/btn_minus"
        app:layout_constraintBottom_toBottomOf="@id/btn_minus"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="VIEW_MODEL"
        android:textColor="@color/purple_500"
        android:textStyle="bold"
        app:layout_constraintLeft_toLeftOf="@id/tv_viewmodel"
        app:layout_constraintRight_toRightOf="@id/tv_viewmodel"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/tv_viewmodel"
        app:layout_constraintVertical_bias="1"/>

    <TextView
        android:id="@+id/tv_viewmodel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        android:textColor="#000"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/tv_normal"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="NORMAL"
        android:textColor="@color/purple_500"
        android:textStyle="bold"
        app:layout_constraintLeft_toLeftOf="@id/tv_normal"
        app:layout_constraintRight_toRightOf="@id/tv_normal"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/tv_normal"
        app:layout_constraintVertical_bias="1"/>

    <TextView
        android:id="@+id/tv_normal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        android:textColor="#000"
        app:layout_constraintLeft_toRightOf="@id/tv_viewmodel"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>





</androidx.constraintlayout.widget.ConstraintLayout>

하단의 증감 버튼을 누르면 숫자가 표시된다.

하나는 뷰모델을 이용하였고, 하나는 Activity의 전역변수로 선언하여 사용했다.

증감버튼을 누른 다음 화면을 회전시키거나 하면 ViewModel의 카운트는 그대로 유지되지만

Activity의 전역변수는 다시 초기값으로 돌아가는 것을 확인할 수 있다.

원격 브랜치가 삭제가 안되면 github에 default branch로 설정되어있는지 확인하고

default 브랜치로 설정되어있다면 다른 브랜치로 설정한 다음 삭제하면 됨

근데 default 브랜치 변경하는 게 좋은 방법인지는 모르겠음

나는 그냥 맨 처음에 잘못 저장한 거 때문에 바로 바꿨다만..

프로젝트 중간에 원격 저장소를 삭제하는 경우라면 잘 생각하고 바꾸길..

 

이전에 했던 코드(https://relz.tistory.com/64)에서 테스트용으로 넣었던 저장&불러오기를 없애고

사칙연산 버전으로 변경하였음

 

1. Contract

package com.monkey.mvpstudy

interface Contract {
    interface View {
        fun setValue(num1: Int, num2: Int, num3: Int)
        fun showResult(num: Int)
    }

    interface Presenter {
        fun getSum(num1: Int, num2: Int)
        fun getSubtract(num1: Int, num2: Int)
        fun getMultiply(num1: Int, num2: Int)
        fun getDivide(num1: Int, num2: Int)
    }
}

 

 

2. MainModel

class MainModel {
    var num1 = 0
    var num2 = 0

    fun plus() : () -> Int = { num1 + num2 }
    fun subtract() : () -> Int = { num1 - num2 }
    fun multiply() : () -> Int = { num1 * num2 }
    fun divide() : () -> Int = { num1 / num2 }
}

 

3. MainActivity

package com.monkey.mvpstudy

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import com.monkey.mvpstudy.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity(), Contract.View, View.OnClickListener {
    private val binding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }
    private val presenter by lazy {
        MyPresenter(this)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.btnSum.setOnClickListener(this)
        binding.btnSubtract.setOnClickListener(this)
        binding.btnMultiply.setOnClickListener(this)
        binding.btnDivide.setOnClickListener(this)
    }

    override fun setValue(num1: Int, num2: Int, num3: Int) {
        binding.etNum1.setText(num1.toString())
        binding.etNum2.setText(num2.toString())
        showResult(num3)
    }

    override fun showResult(num: Int) {
        binding.tvResult.text = num.toString()
    }

    override fun onClick(v: View?) {
        val num1 = binding.etNum1.text.toString().toInt()
        val num2 = binding.etNum2.text.toString().toInt()

        when(v) {
            binding.btnSum -> presenter.getSum(num1, num2)
            binding.btnSubtract -> presenter.getSubtract(num1, num2)
            binding.btnMultiply -> presenter.getMultiply(num1, num2)
            binding.btnDivide -> presenter.getDivide(num1, num2)
        }
    }
}

 

4. MyPresent

package com.monkey.mvpstudy

class MyPresenter : Contract.Presenter {

    private var view : Contract.View
    private var model : MainModel

    constructor(view: Contract.View) {
        this.view = view
        this.model = MainModel()
    }

    override fun getSum(num1: Int, num2: Int) {
        val result = calculate(num1, num2, model.plus())
        view.showResult(result)
    }

    override fun getSubtract(num1: Int, num2: Int) {
        val result = calculate(num1, num2, model.subtract())
        view.showResult(result)
    }

    override fun getMultiply(num1: Int, num2: Int) {
        val result = calculate(num1, num2, model.multiply())
        view.showResult(result)
    }

    override fun getDivide(num1: Int, num2: Int) {
        val result = calculate(num1, num2, model.divide())
        view.showResult(result)
    }


    private fun calculate(num1: Int, num2: Int, calc: () -> Int): Int {
        model.num1 = num1
        model.num2 = num2
        return calc()
    }
}

두 수를 입력받아 합을 출력하는 간단한 프로그램이다.

 

# 전체 구조

 

0. layout

- EditText 두 개, Button 하나, TextView 하나로 구성

 

 

1. MyApplication

package com.monkey.mvpstudy

import android.app.Application
import android.content.Context
import android.content.SharedPreferences

class MyApplication : Application() {
    companion object {
        lateinit var context: Context
        lateinit var pref: SharedPreferences
    }

    init {
        context = this
    }
}

- SharedPrefence를 사용하기 위해서 사용

- Manifest의 <application> 태그 내에 android:name=".Myapplication"으로 사용

 

 

2. Contract

package com.monkey.mvpstudy

interface Contract {
    interface View {
        fun setValue(num1: Int, num2: Int, num3: Int)
        fun showResult(num: Int)
    }

    interface Presenter {
        fun getSum(num1: Int, num2: Int)
        fun save(num1: Int, num2: Int)
        fun load()
    }
}

- 뷰단에서는 값을 설정하고 결과를 보여주도록 할 것이다.

- 프레젠터단에서는 합을 구하고 저장하고 불러오도록 할 것이다.

 

 

3. MainModel

package com.monkey.mvpstudy

class MainModel {
    val presenter: Contract.Presenter

    constructor(presenter: Contract.Presenter) {
        this.presenter = presenter
    }

    fun getSum(num1: Int, num2: Int): Int {
        return num1+num2
    }
}

- 모델에서는 프레젠터를 참고하고 있음

- getSum이라는 함수로 합계를 구하도록 하였음

 

 

4. MainActivity

package com.monkey.mvpstudy

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.monkey.mvpstudy.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity(), Contract.View {
    val binding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }
    val presenter by lazy {
        MyPresenter(this)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        MyApplication.pref = getSharedPreferences("myApp", MODE_PRIVATE)

        presenter.load()    //SharedPreference에 저장한 값을 세팅

        binding.btnOk.setOnClickListener {
            val num1 = binding.etNum1.text.toString().toInt()
            val num2 = binding.etNum2.text.toString().toInt()
            presenter.getSum(num1, num2)
            presenter.save(num1, num2)
        }
    }

    override fun setValue(num1: Int, num2: Int, num3: Int) {
        binding.etNum1.setText(num1.toString())
        binding.etNum2.setText(num2.toString())
        showResult(num3)
    }

    override fun showResult(num: Int) {
        binding.tvResult.text = num.toString()
    }
}

- 맨 처음 onCreate 호출 시 저장한 값을 불러오도록 함

- 버튼을 누르면 presenter에게 합을 구하고 값을 저장하도록 함

- Contract의 View를 상속받았으므로 setValue와 showResult 구현

 

 

5. MyPresenter

package com.monkey.mvpstudy

import androidx.core.content.edit

class MyPresenter : Contract.Presenter {
    companion object {
        val _P_NUMBER1 = "number1"
        val _P_NUMBER2 = "number2"
    }
    var view : Contract.View
    var model : MainModel

    constructor(view: Contract.View) {
        this.view = view
        this.model = MainModel(this)
    }

    override fun getSum(num1: Int, num2: Int) {
        val sum = model.getSum(num1, num2)
        view.showResult(sum)
    }

    override fun save(num1: Int, num2: Int) {
        MyApplication.pref.edit {
            putInt(_P_NUMBER1, num1)
            putInt(_P_NUMBER2, num2)
        }
    }

    override fun load() {
        val num1 = MyApplication.pref.getInt(_P_NUMBER1, 0)
        val num2 = MyApplication.pref.getInt(_P_NUMBER2, 0)
        val sum = model.getSum(num1, num2)
        view.setValue(num1, num2, sum)
    }
}

- Contract의 Presenter을 상속받아 getSum, save, load를 구현

- getSum은 코드의 중복과 확장성을 위해 model의 getSum으로 로직을 옮김

 

 

 

 

 

 

 

+ Recent posts