본문 바로가기
Android

[안드로이드] MVVM 패턴 LiveData + ViewModel + DataBinding 예제

by Sky Titan 2020. 9. 2.
728x90
 

LiveData 개요  |  Android 개발자  |  Android Developers

LiveData를 사용하여 수명 주기를 인식하는 방식으로 데이터를 처리합니다.

developer.android.com

 AAC의 핵심 요소들인 LiveData + ViewModel + Databinding를 이용하여 MVVM 패턴의 예제를 한 번 만들어 보겠습니다. 

 

설명

 우선 만들 예제는 MainActivity와 그 안에 ViewPager를 컨테이너로 삼아 들어있는 3개의 BlankFragment들로 구성되어있으며 프래그먼트들이 부모 액티비티의 ViewModel에 있는 LiveData를 binding 해오는 프로그램입니다.

 data가 MutableLiveData이며 Integer 값입니다. MainActivity에서 버튼을 클릭할 때마다 data값이 증가하고 증가되는 값을 Fragment들에서 표시해주는 겁니다.

 

 

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

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        app:layout_constraintTop_toTopOf="parent"
        />

    <Button
        android:id="@+id/main_button"
        android:text="증가"
        app:layout_constraintTop_toBottomOf="@id/viewpager"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</androidx.constraintlayout.widget.ConstraintLayout>

 MainActivity의 레이아웃입니다. 프래그먼트들을 넣을 ViewPager와 data 값을 증가시킬 역할을 하는 Button으로 구성되어있습니다.

 

MainActivity.class

package org.techtown.test;

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


import androidx.appcompat.app.AppCompatActivity;

import androidx.lifecycle.ViewModelProviders;
import androidx.viewpager2.widget.ViewPager2;


public class MainActivity extends AppCompatActivity {

    private BlankFragment[] fragments = {BlankFragment.newInstance(1), BlankFragment.newInstance(2), BlankFragment.newInstance(3)};

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

        MainViewModel viewModel = ViewModelProviders.of(this).get(MainViewModel.class);

        setViewPager();

        //버튼을 클릭하면 viewModel의 mutable data의 값이 1씩 증가한다.
        Button btn = (Button)findViewById(R.id.main_button);
        btn.setOnClickListener(view -> {
            viewModel.add();
        });
    }

    private void setViewPager()
    {
        ViewPager2 viewPager2 = (ViewPager2) findViewById(R.id.viewpager);

        ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager(), getLifecycle());
        adapter.setList(fragments);
        viewPager2.setAdapter(adapter);
    }

}

 MainActivity에서 viewModel을 최초로 생성합니다. 버튼의 클릭 리스너를 적용하여 클릭될 때마다 data의 값을 1씩 증가시킵니다.

 

 

MainViewModel.java

package org.techtown.test;

import android.util.Log;

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

public class MainViewModel extends ViewModel {

    private MutableLiveData<Integer> data;

    //data binding시 필요
    public LiveData<Integer> getMutableData()
    {
        if(data == null)
            data = new MutableLiveData(0);

        return data;
    }

    //값 증가
    public void add()
    {
        data.setValue(data.getValue()+1);
        Log.i("MainViewModel", data.getValue().toString());
    }
}

 MutableData를 반환하는 getMutableData 메서드와 data 값을 증가시키는 add메서드로 구성되어있습니다. data는 MutableLiveData<Integer> 형입니다.

 

ViewPagerAdapter.java

package org.techtown.test;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager2.adapter.FragmentStateAdapter;

import java.util.ArrayList;
import java.util.Arrays;

public class ViewPagerAdapter extends FragmentStateAdapter {

    ArrayList<Fragment> list = new ArrayList<>();


    public ViewPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
        super(fragmentManager, lifecycle);
    }


    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return list.get(position);
    }

    public void addFragment(Fragment fragment)
    {
        list.add(fragment);
    }

    public void setList(Fragment[] fragments){
        list.clear();
        list.addAll(Arrays.asList(fragments));
    }


    @Override
    public int getItemCount() {
        return list.size();
    }
}

 

fragment_blank.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
    <variable
        name="viewModel"
        type="org.techtown.test.MainViewModel" />
        <variable
            name="fragmentNumber"
            type="String" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:background="@color/limegreen"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".BlankFragment">

        <TextView
            android:id="@+id/number_fragment"
            android:text="@{fragmentNumber}"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toTopOf="@id/textview_fragment"
            android:layout_marginBottom="20dp"
            android:textColor="@color/white"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <TextView
            android:id="@+id/textview_fragment"
            android:textColor="@color/white"
            android:text="@{viewModel.mutableData.toString()}"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>



    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

 BlankFragment의 레이아웃입니다. data의 값을 보여줄 TextView와 현재 프래그먼트의 일련 번호를 보여주는 textView가 있습니다. MainActivity와 영역을 구분하기 위해서 레이아웃의 배경색을 녹색으로 바꿔놨습니다. 

 

 databinding을 이용해 TextView의 text를 viewModel의 mutableData값으로 설정해줍니다. 여기서 주의할 점은 현재 MutableData는 Integer형이기 때문에 toString()을 꼭 사용해야합니다.

 

 

BlankFragment.java

package org.techtown.test;

import android.os.Bundle;

import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProviders;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import org.techtown.test.databinding.FragmentBlankBinding;


public class BlankFragment extends Fragment {


    private View view;
    private String fragment_number = "Fragment ";

    public BlankFragment() {
        // Required empty public constructor
    }

    public static BlankFragment newInstance(int fragment_number) {
        BlankFragment fragment = new BlankFragment();
        Bundle args = new Bundle();

        args.putString("fragment_number",fragment_number+"");

        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if(getArguments() != null)
        {
            fragment_number += getArguments().getString("fragment_number");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        FragmentBlankBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_blank, container, false);
        view = binding.getRoot();

        //부모 액티비티의 viewModel 가져옴
        MainViewModel viewModel = ViewModelProviders.of(getActivity()).get(MainViewModel.class);
        binding.setFragmentNumber(fragment_number);
        binding.setViewModel(viewModel);
        binding.setLifecycleOwner(this);


        return view;
    }

}

 BlankFragment에서는 getActivity()를 활용해서 부모 액티비티(MainActivity)의 viewModel을 가져옵니다. 그리고 binding 객체의 ViewModel에 적용시킵니다.

 fragment_number는 MainActivity에서 받은 파라미터값으로 적용해줍니다.

 이 때 꼭 binding의 LifecycleOwner를 적용해주어야합니다. 안 그러면 binding이 동작하지 않습니다.

 

 

결과화면

 binding하고 있는 부모 액티비티 viewModel의 LiveData가 변하면서 실시간으로 적용되는 모습입니다.

728x90

댓글