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가 변하면서 실시간으로 적용되는 모습입니다.
'Android' 카테고리의 다른 글
[안드로이드] layout 종류 (0) | 2020.09.08 |
---|---|
[안드로이드] 이미지(bitmap)에서의 색추출 : Palette (0) | 2020.09.08 |
[안드로이드] RecyclerView에 itemClickListener (0) | 2020.08.23 |
[안드로이드] RecyclerView (0) | 2020.08.23 |
[안드로이드] ART (0) | 2020.08.23 |
댓글