안드로이드 With Java #31 라이브러리 커스터마이징 (Library Customizing)

Jake Seo·2020년 9월 14일
0

안드로이드 With Java

목록 보기
31/31

안드로이드 With Java #31 라이브러리 커스터마이징 (Library Customizing)

개발 환경

  • sdk 버전 29

개요

안드로이드 개발 중에 라이브러리를 이용했을 때, 라이브러리 동작 중에 마음에 안드는 부분이 있을 수 있다.

이를테면, PFLockScreen이라는 라이브러리를 사용하다가 다음과 같은 문제가 있었다.

PFLockScreenPIN 번호를 생성하고 인증하는 과정에 도움을 주는 라이브러리인데 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이 잘 생성된다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글