안드로이드 With Java #31 라이브러리 커스터마이징 (Library Customizing)
안드로이드 개발 중에 라이브러리를 이용했을 때, 라이브러리 동작 중에 마음에 안드는 부분이 있을 수 있다.
이를테면, PFLockScreen
이라는 라이브러리를 사용하다가 다음과 같은 문제가 있었다.
PFLockScreen
은 PIN
번호를 생성하고 인증하는 과정에 도움을 주는 라이브러리인데 PIN
번호를 생성할 때 진행을 위해 반드시 다음
버튼을 눌러야만 했다.
다음
버튼 없이 생성을 진행하는 방식으로 커스터마이징하고 싶었다.
안드로이드의 라이브러리들은
~ > .gradle > caches > modules-2 > files-2.1
위치에 존재한다.
다만, 라이브러리의 소스를 직접 바꾼다는 접근은 안 하는 것이 좋다. 소스코드들은 이미 jar
와 같이 컴파일된 방식으로 묶여있으며, 만일 바꾼다 해도 라이브러리를 다운받고 바뀐 부분을 적용해주는 것을 계속 반복해주어야 하기 때문에 비효율적일 것이다.
대신에 우리는 자바에서 제공하는 상속 이라는 개념을 사용할 수 있다.
PFLockScreen
라이브러리 내에 메인 로직이 들어있는 클래스인 PFLockScreenFragment
를 상속받아서 커스터마이징된 PFLockScreenFragment
를 재구성하면 된다.
public class CustomizedPFLockScreenFragment extends PFLockScreenFragment {
}
먼저 위와 같이 기존 클래스를 상속받는 클래스를 작성한다.
그리고 Ctrl
+
Click
인스트럭션을 통하여, PFLockScreenFragment
글자를 클릭하면 내부 소스를 볼 수 있다.
위와 같이 내부 소스가 보이면 여기서 우리가 필요한 부분만 재정의해주면 된다.
이번 경우에는 다음
버튼을 눌러야 동작하던 로직을 PIN
번호 입력이 완료되었을 때 동작하게만 바꿔주면 됐다.
아래와 같이 코드를 작성해주었다.
public class CustomizedPFLockScreenFragment extends PFLockScreenFragment {
private static final String TAG = PFLockScreenFragment.class.getName();
private PFCodeView mCodeView;
private OnPFLockScreenLoginListener mLoginListener;
private boolean nIsCreateMode = false;
private OnPFLockScreenCodeCreateListener mCodeCreateListener;
private String mCode = "";
private String mEncodedPinCode = "";
private String nCodeValidation = "";
private PFFLockScreenConfiguration nConfiguration;
private TextView titleView;
private final PFPinCodeViewModel mPFPinCodeViewModel = new PFPinCodeViewModel();
public void setEncodedPinCode(String encodedPinCode) {
mEncodedPinCode = encodedPinCode;
}
public void setCodeCreateListener(OnPFLockScreenCodeCreateListener listener) {
mCodeCreateListener = listener;
}
public void setLoginListener(OnPFLockScreenLoginListener listener) {
mLoginListener = listener;
}
public void setConfiguration2(PFFLockScreenConfiguration configuration) {
this.nConfiguration = configuration;
}
private void cleanCode() {
mCode = "";
mCodeView.clearCode();
}
private final PFCodeView.OnPFCodeListener mCodeListener = new PFCodeView.OnPFCodeListener() {
@Override
public void onCodeCompleted(String code) {
nIsCreateMode = nConfiguration.getMode() == PFFLockScreenConfiguration.MODE_CREATE;
if (nIsCreateMode) {
mCode = code;
if (nConfiguration.isNewCodeValidation() && TextUtils.isEmpty(nCodeValidation)) {
nCodeValidation = mCode;
cleanCode();
titleView.setText(nConfiguration.getNewCodeValidationTitle());
return;
}
if (nConfiguration.isNewCodeValidation() && !TextUtils.isEmpty(nCodeValidation) && !mCode.equals(nCodeValidation)) {
mCodeCreateListener.onNewCodeValidationFailed();
titleView.setText(nConfiguration.getNewCodeValidationTitle());
cleanCode();
return;
}
nCodeValidation = "";
mPFPinCodeViewModel.encodePin(getContext(), mCode).observe(
CustomizedPFLockScreenFragment.this,
new Observer<PFResult<String>>() {
@Override
public void onChanged(@Nullable PFResult<String> result) {
if (result == null) {
return;
}
if (result.getError() != null) {
Log.d(TAG, "Can not encode pin code");
deleteEncodeKey();
return;
}
final String encodedCode = result.getResult();
if (mCodeCreateListener != null) {
mCodeCreateListener.onCodeCreated(encodedCode);
}
}
}
);
return;
}
mCode = code;
mPFPinCodeViewModel.checkPin(getContext(), mEncodedPinCode, mCode).observe(
CustomizedPFLockScreenFragment.this,
new Observer<PFResult<Boolean>>() {
@Override
public void onChanged(@Nullable PFResult<Boolean> result) {
if (result == null) {
return;
}
if (result.getError() != null) {
return;
}
final boolean isCorrect = result.getResult();
if (mLoginListener != null) {
if (isCorrect) {
mLoginListener.onCodeInputSuccessful();
} else {
mLoginListener.onPinLoginFailed();
errorAction();
}
}
if (!isCorrect && nConfiguration.isClearCodeOnError()) {
mCodeView.clearCode();
}
}
});
}
@Override
public void onCodeNotCompleted(String code) {
if (nIsCreateMode) {
return;
}
}
};
private void deleteEncodeKey() {
mPFPinCodeViewModel.delete().observe(
this,
new Observer<PFResult<Boolean>>() {
@Override
public void onChanged(@Nullable PFResult<Boolean> result) {
if (result == null) {
return;
}
if (result.getError() != null) {
Log.d(TAG, "Can not delete the alias");
return;
}
}
}
);
}
private void errorAction() {
if (nConfiguration.isErrorVibration()) {
final Vibrator v = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
if (v != null) {
v.vibrate(400);
}
}
if (nConfiguration.isErrorAnimation()) {
final Animation animShake = AnimationUtils.loadAnimation(getContext(), R.anim.shake_pf);
mCodeView.startAnimation(animShake);
}
cleanCode();
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
mCodeView = getView().findViewById(R.id.code_view);
titleView = getView().findViewById(R.id.title_text_view);
mCodeView.setListener(mCodeListener);
super.onActivityCreated(savedInstanceState);
}
}
수정이 필요하지 않은 부분은 건들지 않으면 상속받은 그대로 동작하니 건들지 않으면 된다.
해당 클래스를 상속받아 사용하는데 대부분의 변수가 private
로 선언되어 있었다. private
으로 선언된 변수들은 상속한 클래스에서 건들 수 없다. 그래서 최대한 getter
, setter
를 이용해야 하는데, getter
는 없었고 setter
만 있었다.
그래서 위와 같이 소스코드가 약간 더럽게 나온 감이 있다.
이전과 달리 다음
버튼을 누르지 않아도 PIN
이 잘 생성된다.