원본 소스 경로
https://github.com/JsonCorp/ViewModel
1. 개요
ViewModel 을 사용 할 경우 UI 관련 데이터를 저장하고 관리하고 화면 회전과 같이 구성을 변경할 때도 데이터를 유지 할 수 있음.
2. ViewModel 사용하지 않을 경우 문제점.
- UI Controller를 제거 하거나 다시 만들 경우 UI 관련 데이터 손실
- 전달 데이터가 단순할 경우 문제가 없지만 대용량일 경우 UI 딜레이 발생
- 비동기 호출로 인한 데이터, 메모리 관리 필요로 인한 유지 보수 비용가 필요하여 리소스 낭비
- UI Controller에서 데이터를 관리 할 경우 UI Controller 코드 복잡도 증가.
3. ViewModel 생명 주기
- Activity 실행 후 화면 회전 및 Fragment 화면 변경 할 경우 데이터 유지됨.
4. ViewModel 구조
- 기존 UI Controller
> UI 화면 별로 데이터를 가져와야함.
- ViewModel
> UI에서는 Activity 실행 후 각 화면별로 ViewModel에 데이터를 저장하여 공유
5. Fragment간 데이터 공유
- Activity 실행 후 ViewModel 생성
- 각 Fragment에서 데이터를 ViewModel 에 저장
- Fragment에서는 ViewModelProvider를 통해 변경된 데이터를 업데이트 할 수 있음.
6. 샘플 소스
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.json.viewmodel">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
build.gralde (app)
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.json.viewmodel"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
// Saved state module for ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
// Annotation processor
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
// alternately - if using Java8, use the following instead of lifecycle-compiler
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/main_master_fragment_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Master Fragment" />
<Button
android:id="@+id/main_detail_fragment_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Detail Fragment" />
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
master_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FAED7D"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="30sp"
android:gravity="center"
android:textSize="30sp"
android:text="Master Fragment"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/master_add_data_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Data"/>
<EditText
android:id="@+id/master_input_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Input Text"/>
</LinearLayout>
<TextView
android:id="@+id/master_info_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="N/A"/>
</LinearLayout>
detail_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#B2CCFF"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="30sp"
android:gravity="center"
android:textSize="30sp"
android:text="Detail Fragment"/>
<TextView
android:id="@+id/detail_info_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="N/A"/>
</LinearLayout>
MainActivity.java
package com.json.viewmodel;
import android.os.Bundle;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
/**
* https://developer.android.com/topic/libraries/architecture/viewmodel#java
* https://developer.android.com/jetpack/androidx/releases/lifecycle#declaring_dependencies
*/
public class MainActivity extends AppCompatActivity {
private MasterFragment mMasterFragment;
private DetailFragment mDetailFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button masterFragmentButton = findViewById(R.id.main_master_fragment_button);
Button detailFragmentButton = findViewById(R.id.main_detail_fragment_button);
mMasterFragment = new MasterFragment();
mDetailFragment = new DetailFragment();
masterFragmentButton.setOnClickListener(View -> changeFragment(mMasterFragment));
detailFragmentButton.setOnClickListener(View -> changeFragment(mDetailFragment));
// 최초 화면에 표시할 Fragment 화면
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, mMasterFragment, MasterFragment.TAG).commit();
}
}
// 버튼을 누를 경우 선택한 Fragment 변경
public void changeFragment(Fragment fragment) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, fragment).commit();
}
}
SharedViewModel.java
package com.json.viewmodel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
MasterFragment.java
package com.json.viewmodel;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
public class MasterFragment extends Fragment {
public static String TAG = "MasterFragment:";
private SharedViewModel model;
private Button mAddDataButton;
private TextView mInfoTextView;
private TextView mDataEditView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.master_fragment, container, false);
mAddDataButton = view.findViewById(R.id.master_add_data_button);
mInfoTextView = view.findViewById(R.id.master_info_text_view);
mDataEditView = view.findViewById(R.id.master_input_edit_text);
return view;
}
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
mAddDataButton.setOnClickListener(item -> {
Item inputItem = new Item();
inputItem.setName(mDataEditView.getText().toString());
model.select(inputItem);
mDataEditView.setText("");
});
model.getSelected().observe(getViewLifecycleOwner(), item -> {
mInfoTextView.setText(item.getName());
});
}
}
DetailFragment.java
package com.json.viewmodel;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
public class DetailFragment extends Fragment {
public String TAG = "DetailFragment:";
private TextView mDetailInfoView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.detail_fragment, container, false);
mDetailInfoView = view.findViewById(R.id.detail_info_text_view);
return view;
}
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
model.getSelected().observe(getViewLifecycleOwner(), item -> {
mDetailInfoView.setText(item.getName());
});
}
}
Item.java
package com.json.viewmodel;
public class Item {
private String mName;
private String mNumber;
public void setName(String name) {
mName = name;
}
public void setNumber(String number) {
mNumber = number;
}
public String getName() {
return mName;
}
public String getNumber() {
return mNumber;
}
}
7. 화면 구성
- 메인 화면 실행
- Master Fragment 데이터 입력
- Detail Fragment 화면 이동
> 화면 이동 할 경우 Master Fragment에서 입력한 데이터 갱신
- Master Fragment 돌아가기
> ViewModel 데이터가 삭제되지 않고 유지되어 있음.
Android 공식 가이드 문서
https://developer.android.com/topic/libraries/architecture/viewmodel#java
'Android > Sample Source' 카테고리의 다른 글
[안드로이드] Thread, TimerTask, Handler 반복 호출 만들기 JAVA (0) | 2020.09.21 |
---|---|
[안드로이드] SQL DB 샘플 소스 (38) | 2020.05.21 |