티스토리 뷰
요구사항
- 화면에 키와 몸무게 입력을 요구하는 텍스트와 BMI를 계산하는 버튼이 존재 한다.
- 버튼을 누르면 BMI 값이 계산되고, 백그라운드에서 저장 처리를 시작한다(저장 처리중에는 버튼이 비활성화 된다)
- 저장 처리를 마치면 버튼이 다시 활성화 된다
----------------------------------------------------------------------------------------------------
클래스 설계
MainActivity
1. 입력된 값을 BmiCalculator 에 계산을 요청한다
2. BmiCalculator에서 계산되어 넘어온 값을 BmiSaveService 에 전달한다
3. BMI 값을 저장한다
4. 저장을 마치면 그 결과값을 앱 내에 BroadCast 된 메시지를 전달 받아 처리 한다
BmiCalculator
1. BMI값을 계산한다
2. BmiValue 객체에게 값을 전달한후 객체로 받는다.
BimValue
1. Serializable을 구현한다 (객체 자체를 인텐트로 넘겨 받기 위함)
2. 설정값을 정의 한다
3. 지수값을 호출하는 메소드를 구현한다
4. 메세지를 출력하는 메소드를 구현한다.
SaveBmiService
1. IntentService 를 상속한다
: 인텐트서비스란 비동기로 실행되는 쓰레드인데, 여러번 실행되었을 경우에는 Queue로 처리된다.
또한 Queue에 들어있는 IntentService가 모두 종료되면 그 때 onDestroy 가 호출된다.
2. LocalBroadcastManager 를 멤버 변수로 선언 한다.
3. 결과값을 저장하는 로직을 만든다
4. 처리가 완료 되면 sandLocalBroadcast() 메소드를 호출 한다
메인 액티비티
: 테스트를 위한 코드가 포함되어 있지만 다음에 설명하기로 한다
public class MainActivity extends AppCompatActivity {
private LocalBroadcastManager mLocalBroadcastManager;
// 브로드케스트를 받기 위한 메니져를 선언
private Button mCalcButton;
// 계산 버튼
private BroadcastReceiver mReceiver;
// 브로드케스트 선언
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 메인액티비티 생성시 로컬브로드케스트를 할당해 준다.
mLocalBroadcastManager = LocalBroadcastManager.getInstance(getApplicationContext());
initViews();
}
@VisibleForTesting
void initViews() {
EditText weightText = (EditText)findViewById(R.id.text_weight);
EditText heightText = (EditText)findViewById(R.id.text_height);
TextView resultText = (TextView)findViewById(R.id.text_result);
mCalcButton = (Button)findViewById(R.id.button_calculate);
// 클릭리스너를 사용 정의에 맞게 구현한 부분이다 (여러가지 로직처리를 묶어 낼 수 있다)
View.OnClickListener buttonListener = createButtonListener(weightText, heightText, resultText);
mCalcButton.setOnClickListener(buttonListener);
}
@VisibleForTesting
View.OnClickListener createButtonListener(final TextView weightText,
final TextView heightText,
final TextView resultText) {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
//BmiCalculator 객체에게 등록된 값을 전달하고 결과값으로 BmiValue 객체를 받는다.
BmiValue result = calculateBmiValue(weightText, heightText);
// 처리되어 온 객체를 전달 받고 표시 해주는 부분이다
showCalcResult(resultText, result);
//Service를 사용해 보존 처리
startResultSaveService(result);
//브로드 케스트 메세지를 받을 인텐트 필터를 선언하고 리시버 객체를 등록한다.
prepareReceiveResultSaveServiceAction();
}
};
}
@VisibleForTesting
BmiValue calculateBmiValue(final TextView weightText, final TextView heightText) {
float weight = Float.valueOf(weightText.getText().toString());
float height = Float.valueOf(heightText.getText().toString());
// 계산 객체를 생성해서 메소드를 메소드를 호출 한다
// 리턴값은 BmiCalculator 객체가 아닌 생성된 BmiValue 객체이다.
BmiCalculator calculator = new BmiCalculator();
return calculator.calculate(height, weight);
}
@VisibleForTesting
void showCalcResult(TextView resultText, BmiValue result) {
String message = result.toFloat() + " : " + result.getMessage() + "체형입니다";
resultText.setText(message);
}
@VisibleForTesting
void startResultSaveService(BmiValue result) {
mCalcButton.setText("저장 중입니다...");
mCalcButton.setEnabled(false);
// 서비스에게 저장 처리을 요청한다.
// 추후 서비스가 동작을 끝내고 나면 브로드케스트를 할 것이고
// 메인 객체에서 그 메세지를 받아 로직을 처리 할 것이다.
// static 메소드를 선언해서 좀 더 명확하게 로직을 처리 하였다
SaveBmiService.start(MainActivity.this, result);
}
@VisibleForTesting
void prepareReceiveResultSaveServiceAction() {
IntentFilter filter = new IntentFilter(SaveBmiService.ACTION_RESULT);
if (mReceiver == null) {
mReceiver = new BmiSaveResultReceiver(mCalcButton);
mLocalBroadcastManager.registerReceiver(mReceiver, filter);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReceiver != null) {
// 앱이 종료 되었을 때 자원을 꼭 해제 해주도록 하자
mLocalBroadcastManager.unregisterReceiver(mReceiver);
}
}
@VisibleForTesting
static class BmiSaveResultReceiver extends BroadcastReceiver {
// BroadcastReceiver 의 서브클래스 BmiSaveResultReceiver 만들어
// 로직을 직접 처리하게 하였다
private Button mCalcButton;
BmiSaveResultReceiver(Button calcButton) {
mCalcButton = calcButton;
}
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) {
return;
}
if (!intent.hasExtra(SaveBmiService.PARAM_RESULT)) {
return;
}
boolean result = intent.getBooleanExtra(SaveBmiService.PARAM_RESULT, false);
if (!result) {
Toast.makeText(context, "BMI 저장에 실패했습니다", Toast.LENGTH_SHORT).show();
}
mCalcButton.setText("계산한다");
mCalcButton.setEnabled(true);
}
}
}
BMI를 계산할 객체
BmiCalculator 객체
public class BmiCalculator {
/**
* BMI값을 계산해서 반환한다
* @param heightInMeter BMI 계산에 사용할 신장
* @param weightInKg BMI 계산에 사용할 체중
* @return BMI 값
*/
public BmiValue calculate(float heightInMeter, float weightInKg) {
if (heightInMeter < 0 || weightInKg < 0) {
throw new RuntimeException("키와 몸무게는 양수로 지정해주세요");
}
float bmiValue = weightInKg / (heightInMeter * heightInMeter);
return createValueObj(bmiValue);
}
@VisibleForTesting
BmiValue createValueObj(float bmiValue) {
return new BmiValue(bmiValue);
}
}
결과값으로 전달 받은 객체 처리 (직렬화)
BimValue 객체
public class BmiValue implements Serializable {
private static final long serialVersionUID = -4325336659053219895L;
@VisibleForTesting
static final String THIN = "마른";
@VisibleForTesting
static final String NORMAL = "보통";
@VisibleForTesting
static final String OBESITY = "비만(1도)";
@VisibleForTesting
static final String VERY_OBESITY = "비만(2도)";
private final float mValue;
public BmiValue(float value) {
mValue = value;
}
/**
* 소수점 아래 2자리까지의 부동소수점값
* @return
*/
public float toFloat() {
int rounded = Math.round(mValue * 100);
return rounded / 100f;
}
/**
* BMI에 따른 판정 메시지 반환
*/
public String getMessage() {
if (mValue < 18.5f) {
return THIN;
} else if (18.5 <= mValue && mValue < 25) {
return NORMAL;
} else if (25 <= mValue && mValue < 30) {
return OBESITY;
} else {
return VERY_OBESITY;
}
}
}
서비스 객체
BimValue 객체를 인텐트로 전달 받고 저장 결과를 방송할 객체이다.
SaveBmiService 객체
public class SaveBmiService extends IntentService {
public static final String ACTION_RESULT = SaveBmiService.class.getName() + ".ACTION_RESULT";
public static final String PARAM_RESULT = "param_result";
static final String PARAM_KEY_BMI_VALUE = "bmi_value";
private LocalBroadcastManager mLocalBroadcastManager;
public SaveBmiService() {
super(SaveBmiService.class.getName());
}
@Override
public void onCreate() {
super.onCreate();
mLocalBroadcastManager = LocalBroadcastManager.getInstance(getApplicationContext());
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent == null) {
return;
}
// 인자값으로 BmiValue 객체가 넘어 오기 때문에 직렬화 처리를 한다.
Serializable extra = intent.getSerializableExtra(PARAM_KEY_BMI_VALUE);
if (extra == null || !(extra instanceof BmiValue)) {
return;
}
// BmiValue 객체로 캐스트 한다
BmiValue bmiValue = (BmiValue)extra;
boolean result = saveToRemoteServer(bmiValue);
// 방송을 시작 한다.
sendLocalBroadcast(result);
}
@VisibleForTesting
boolean saveToRemoteServer(BmiValue bmiValue) {
try {
Thread.sleep(3000 + new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
//--------------
//사실은 여기에 서버에 저장하는 처리를 한다
//--------------
return new Random().nextBoolean();
}
@VisibleForTesting
void sendLocalBroadcast(boolean result) {
Intent resultIntent = new Intent(ACTION_RESULT);
resultIntent.putExtra(PARAM_RESULT, result);
mLocalBroadcastManager.sendBroadcast(resultIntent);
}
@VisibleForTesting
void setLocalBroadcastManager(LocalBroadcastManager manager) {
mLocalBroadcastManager = manager;
}
public static void start(Context context, BmiValue bmiValue) {
Intent intent = new Intent(context, SaveBmiService.class);
intent.putExtra(PARAM_KEY_BMI_VALUE, bmiValue);
context.startService(intent);
}
}
xml 파일
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.advanced_android.bmicalculator.MainActivity">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/text_height"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="wrap_content"
android:hint="키(m):1.70 등"
android:inputType="numberDecimal"
/>
<EditText
android:id="@+id/text_weight"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="wrap_content"
android:hint="몸무게(kg)"
android:inputType="numberDecimal"
/>
</LinearLayout>
<Button
android:id="@+id/button_calculate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="계산"
/>
<TextView
android:id="@+id/text_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
'android' 카테고리의 다른 글
16. TabLayout(ViewPager 사용) 구현 (0) | 2018.08.11 |
---|---|
15. NavigationView 사용하기 (0) | 2018.08.10 |
13 mvvm GitHubService (0) | 2018.07.11 |
12.GitHubService Ex (1) | 2018.07.06 |
11.ContentProbider을 알자 (0) | 2018.05.26 |