선생님, 개발을 잘하고 싶어요.

[아틱 프로젝트] 안드로이드 PopupSystem 구축하기 본문

개발/android 개발

[아틱 프로젝트] 안드로이드 PopupSystem 구축하기

알고싶은 승민 2019. 8. 13. 12:08

목적

안드로이드 개발을 하다 보면 빈번히 사용자에게 특정 정보 입력을 강요하는 modeless popup을 띄워주어야 하는 경우가 있다.

 

대화상자 안드로이드 공식 홈페이지를 참고하면 alert dialog에 대해서 알고 손쉽게 popup을 띄워줄 수 있다.

 

하지만 전체 프로젝트에서 여러 번 사용하기엔 코드의 반복이 많고 크고 번거롭다는 생각이 들었다. 아래는 실제 프로젝트에서 적용할 popup 이미지이다.

 

 

복잡하진 않지만 실제 서버 통신까지 포함해야한다.

필자는 우선 안드로이드에서 기본 제공되는 alert dialog는 프로젝트의 요구사항을 만족시키지 못한다는 사실을 알았다. 디자이너가 준 새로운 popup을 띄워줄 필요가 있다.

진행

  필자는 공식 홈페이지의 문서중 사용자 지정 레이아웃 생성을 참조했다. 코드만 따로 가져와서 같이 보면서 얘기해보자.

 

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    // Get the layout inflater
    LayoutInflater inflater = getActivity().getLayoutInflater();

    // Inflate and set the layout for the dialog
    // Pass null as the parent view because its going in the dialog layout
    builder.setView(inflater.inflate(R.layout.dialog_signin, null))
    // Add action buttons
           .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
               @Override
               public void onClick(DialogInterface dialog, int id) {
                   // sign in the user ...
               }
           })
           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                   LoginDialogFragment.this.getDialog().cancel();
               }
           });
    return builder.create();
}

위에서부터 필자가 포인트라 생각한 부분은 다음과 같다.

  1. AlertDialog.Builder 생성자에 activity 참조가 필요하다.
  2. setView안에 View객체를 넣어주어야 한다.
  3. popup을 종료시키기 위해선 dialog.cancel() 호출이 필요하다.
  4. 반환 값인 Dialog객체의 show() 함수를 호출하면 popup이 나타난다.

이제 하나씩 살펴보자.

 

Activity 참조의 필요

popup을 보여주기 위해 activity참조가 필요하다는 부분에서 우리는 우리가 만들 PopupSystem이 activity를 매개변수로 받아야 할 것이라고 추측할 수 있다.

 

View객체 필요

view객체 만들기 위해서 activity의 layoutInflater를 이용할 수 있다. 그리고 view객체를 생성하고 커스텀 버튼 이벤트 연결까지 전부다 해 줄 수 있다.

 

또한 layoutInflater를 사용해서 view를 만드는 코드는 보일러 플레이트 코드이므로 따로 묶어내어 모듈화 할 수 있다.

 

show() 함수 호출 필요

프로그래머 입장에서 popup시스템을 호출한다는 것은 당장 popup을 보여줄 생각이라고 가정하였다. 따라서 반복되는 show() 함수를 없애줄 필요가 있다.

Dialog.cancel()의 호출 필요

커스텀 버튼 이벤트에 dialog.cancel()을 호출하지 않으면 팝업이 종료되지 않고 버튼 이벤트만 발생한다. 따라서 cancel() 호출이 필요하다.

 


그래서 만든 PopupSystem 코드

object PopupSystem {
    /**
     * 버튼을 누르면 dialog를 종료하는 popup을 만든다.
     * @param activity popup이 나타나는 곳
     * @param layoutId dialog의 layout id값
     * @param map key는 click event가 적용될 view, value는 click이벤트
     * */
    fun show(activity: Activity?, layoutId: Int, map: Map<Int, (()->Any)?>) {
        activity?.run {
            val view = layoutInflater.inflate(layoutId, null, false)
            val dialog = AlertDialog.Builder(this)
                .setView(view)
                .create()

            for (pair in map) {
                view.findViewById<View>(pair.key).setOnClickListener {
                    pair.value?.invoke()
                    dialog.dismiss()
                }
            }

            dialog.show()
        }
    }
}

 

layoutInflater를 이용해 우리가 만들 custom view의 layoutId를 넘겨준다. 그러면 귀찮은 보일러 플레이트 코드를 함수 하나에 둘 수 있다.

 

또한 custom view마다 버튼 이벤트를 설정해야 하는 View가 다르기 때문에 유동적으로 <ViewId, ClickEvent>를 map객체로 받아서 만들어줬다. 

 

대신 map객체의 이벤트를 연결하는 과정에서 버튼을 누르면 항상 popup system이 종료되도록 dialog.dismiss()를 구성해 두었다. (유동성이 없는 부분)

 

마지막엔 만든 dialog의 show() 함수를 호출해 activity에 popup을 띄워주었다.

 

 

 

위의 함수를 사용하는 코드

// TODO 아카이브를 제거할 것인지, 이름을 변경할 것인지를 보여줄 팝업을 띄워준다.
PopupSystem.show(this, R.layout.dlg_modify_archive,
    mapOf(
        R.id.btn_dlg_archive_modify to {
            // TODO 아카이브 수정 activity로 이동해야한다.
            toast("아카이브 수정")
        },
        R.id.btn_dlg_archive_delete to  {
            toast("아카이브 제거")
            // TODO 아카이브 제거 popup을 다시 띄워주어야 한다.
            PopupSystem.show(this, R.layout.dlg_confirm_delete_archive,
                mapOf(
                    R.id.btn_dlg_confirm_delete to {
                        // TODO 아카이브 제거 서버 통신을 해야한다.
                        toast("아카이브 제거 확실!")
                    },
                    R.id.btn_dlg_confirm_cancel to {
                        // 팝업창을 종료하면 되니까 아무것도 하지않는다. (PopupSystem에서 알아서 종료시켜준다.)
                    }
                )
            )
        }
    )
)

 

결론

결과적으로 필자의 PopupSystem을 적용하면 custom view의 popup을 선언적으로 프로그래밍해서 사용할 수 있다.

 

또한 activity의 변수를 사용해서 popup이벤트 연결을 손쉽게 할 수 있어서 api call과 ui 갱신을 손쉽게 완수할 수 있다.

Comments