일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Side Menu
- DataBinding
- ViewBuilder
- Android
- 상단 탭바
- pod install
- List
- GeometryReader
- Swift Package Manager
- base64 변환
- 개발자 면접
- swift #swift keychain #keychain 사용법
- UIViewControllerTransitioningDelegate
- url 추적
- transformation.map
- swift
- Tuist
- ios
- convert base64
- SwiftUI
- 스크롤 탭
- url 관찰
- 기존 앱
- development language
- scrolling tab
- detect url
- UIPresentationController
- notifychanged
- DevelopmentRegion
- oberve url
- Today
- Total
버그 잡이
(실전 적용)ViewModel + LiveData로 화면 전환시 data 초기화 문제 해결하기 #configuration change 본문
(실전 적용)ViewModel + LiveData로 화면 전환시 data 초기화 문제 해결하기 #configuration change
버그잡이 2020. 4. 10. 21:49
내가 만들었던 앱 중에서 핸드폰 센서를 이용해서 벌레를 잡는 게임이 있다.
벌레는 4가지 종류가 랜덤으로 선택 되어 나타나고 열심히 흔들어서 카운트가 10까지 세지면 벌레가 잡히는 게임이다.
여기에는 문제가 있는데 화면이 가로/세로 전환이 될때 벌레 이미지와 카운트가 초기화 된다는 것이다.
이번에 배운 ViewModel과 LiveData를 활용해서 이 문제를 해결해보자.
GetBugActivity에서 사용되는 메소드는 다음과 같다.
- selectBug() : bugImg 랜덤 선택
- getBugImg() : bugImg 받아서 업데이트
- showAlert() : 카운트가 10이 될 경우 다이얼로그 띄워주기
- countUp() : 흔들림 감지될때마다 카운트 올리기
+SenSonEventListener에 따른 override 메서드들...
UI / ViewModel 구분하기
1. selectBug() : bugImg 데이터를 결정하는 decision-making 메서드로 -> ViewModel
2. getbugImg() : UI를 참조함으로 -> UI
3. showAlert() : 다이얼로그는 UI와 연결됨으로 -> UI
4. showAlert() : count 데이터를 결정하는 decisiom-making 메서드로 -> ViewModel
5. SensorEventListener : 생명주기에 따라 리스너를 해제해줘야하는데 viewmodel에 넣을 경우 onStop시 리스너 해제가 안 됨으로 UI의 생명주기 메서드를 통해서 통제.
이를 바탕으로 아래의 코드와 같이 작성했다.
*GetBugActivity.class
public class GetBugActivity extends AppCompatActivity implements SensorEventListener {
ImageView iv_getBug;
TextView tv_count;
ConstraintLayout layout_getBug;
private SensorManager sensorManager;
private Sensor countSensor;
Animation animRotate;
int count = 0;
private GetBugViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_get_bug);
iv_getBug = findViewById(R.id.iv_getBug);
tv_count = findViewById(R.id.tv_count);
layout_getBug = findViewById(R.id.layout_getBug);
//뷰모델 설정
viewModel = ViewModelProviders.of(this).get(GetBugViewModel.class);
//버그 이미지 받아서 넣어주기
getBugImg();
viewModel.getCount().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer count) {
//카운트 세주기
tv_count.setText("카운트 : " + count);
//카운트에 따라 배경색 바꿔주기
int colorPercent = count*10;
layout_getBug.setBackgroundColor(Color.argb(colorPercent, 255, 175, 175));
//카운트가 10이 되면 알람창 띄워주고 센서 종료
showAlert(count);
}
});
//벌레 애니메이션
animRotate = AnimationUtils.loadAnimation(this, R.anim.rotate);
//흔들기 센서
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
countSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
}
private void getBugImg(){
//뷰모델에서 버그 이미지 받아서 넣어주기
iv_getBug.setImageResource(viewModel.getBugImg());
}
@Override
protected void onStart() {
super.onStart();
sensorManager.registerListener(this, countSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onResume() {
super.onResume();
iv_getBug.startAnimation(animRotate);
}
@Override
protected void onPause() {
super.onPause();
iv_getBug.clearAnimation(); //Todo. 이미지가 사라진다. 수정하자.
}
@Override
protected void onStop() {
super.onStop();
sensorManager.unregisterListener(this);
}
//카운트가 10이 되면 알람창 띄워주고 센서 종료
private void showAlert(int count){
//카운트가 10이 되면 센서 종료 및 알람창 띄워주기
if(count == 10){
sensorManager.unregisterListener(this);
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("## 벌레를 잡았습니다 ##");
builder.setMessage("잡은 벌레를 캐릭터에게 먹이겠습니까?");
builder.setPositiveButton("예",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
//벌레 등록 액티비티로 이동
Intent intent = new Intent(GetBugActivity.this, EatBugActivity.class);
intent.putExtra("bugImg", viewModel.getBugImg());
startActivity(intent);
finish();
}
});
builder.setNegativeButton("아니오",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
//메인메뉴로 돌아가기
Intent intent = new Intent(GetBugActivity.this, MainActivity.class);
startActivity(intent);
finish();
}
});
builder.show();
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_STEP_DETECTOR) {
//카운트 올리기
viewModel.countUp();
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}
*GetBugViewModel.class
public class GetBugViewModel extends ViewModel {
private int bugImg;
private MutableLiveData<Integer> count;
public GetBugViewModel() {
super();
selectBug();
count = new MutableLiveData<Integer>();
count.setValue(0);
}
public MutableLiveData<Integer> getCount(){
if(count == null){
count = new MutableLiveData<Integer>();
}
return count;
}
public int getBugImg(){
return bugImg;
}
//게임 시작시 랜덤으로 벌레 선택
private int selectBug(){
//랜덤 값에 따라 벌레 이미지 결정
int bugRandom= (int)(Math.random()*10);
if(bugRandom<2){
bugImg = R.drawable.img_bug1;
}
else if(bugRandom >= 2 && bugRandom<5){
bugImg = R.drawable.img_bug2;
}
else if(bugRandom >= 5 && bugRandom <8){
bugImg = R.drawable.img_bug3;
}
else{
bugImg = R.drawable.img_bug4;
}
return bugImg;
}
//걸음수가 인식되면 count 올리기
public void countUp(){
count.setValue(count.getValue() + 1);
}
}
느낀점
1. 부끄럽지만 의식적으로 private를 활용하여 데이터를 보호한 것은 이번이 처음이다.
- 앞으로도 객체 내의 데이터를 잘 보호하자.
2. 코드가 깔끔해지는 것을 느꼈다.
- 지저분했던 나의 방을 정리하는 기분이었다.
- 정리가 되니 구조가 확실히 더 잘 보인다.
- 지금 프로젝트는 코드가 적으니 큰 차이 없지만 프로젝트의 규모가 커졌을때 큰 힘을 발휘할 것 같다.
'안드로이드' 카테고리의 다른 글
EditText에서 한줄로 입력하기 #singleLine을 대체하는 inputType (0) | 2020.04.22 |
---|---|
Kotlin + Retrofit + Moshi 중첩된 Json 풀기 #nested json (0) | 2020.04.16 |
[Udacity android with kotlin]4. LifeCycle #안드로이드 생명주기 (0) | 2020.04.10 |
[Android] 안드로이드 애니메이션 종류 #Property #View #Transition (1) | 2020.04.07 |
중첩리사이클러뷰 recyclerView 안에 recyclerView 넣기 #야놀자 앱 레이아웃 #nested recycelerview (2) | 2020.04.03 |