안드로이드에서 메일 보내기

2023. 5. 5. 21:15Mobile/Android

작성자알 수 없는 사용자

728x90
반응형

안녕하세요. 기깔나는 사람들에서 안드로이드를 맡고 있는 마플입니다.

이번에는 안드로이드에서 Firebase를 거쳐 메일을 보내는 것을 해보겠습니다. 

상황은 반려되는 내용을 메일로 보내야 합니다.

서버는 Node.js에서 Nodemailer를 통해 Google계정으로 메일 보내기를 참고해주세요. 


거절 메시지 보내는 것을 화면의 아래에서 나오도록 하게 할겁니다.

해당 view를 위한 껍데기를 만들어야 겠죠?

Layout

1. component_reject_select

거절하는 이유를 대표적인 것을 3개로 보여주고 그외 것을 입력하고 싶으면 기타를 통해 입력 할 수 있도록 만들었습니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.slidingpanelayout.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_gravity="bottom"
    android:background="@color/white"
    app:layout_constraintBottom_toBottomOf="parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >
        <TextView
            android:id="@+id/infoFormRejectTitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="답변을 선택해 주세요."
            android:textAlignment="center"
            android:textSize="20sp"
            android:textStyle="bold" />

        <RadioGroup
            android:id="@+id/infoFormRejectRadioGroup"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <RadioButton
                android:id="@+id/infoFormRejectRadioOption1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/info_form_reject_reason_option_1" />

            <RadioButton
                android:id="@+id/infoFormRejectRadioOption2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/info_form_reject_reason_option_2" />

            <RadioButton
                android:id="@+id/infoFormRejectRadioOption3"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/info_form_reject_reason_option_3" />

            <RadioButton
                android:id="@+id/infoFormRejectRadioOption4"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/info_form_reject_reason_option_4" />
        </RadioGroup>
        <EditText
            android:id="@+id/infoFormRejectReasonEditText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="기타 사유를 적어 주세요."
            android:selectAllOnFocus="true"
            />
        <Button
            android:id="@+id/infoFormRejectAcceptButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="보내기"
            android:enabled="false"
            android:layout_margin="8dp"
            />
    </LinearLayout>


</androidx.slidingpanelayout.widget.SlidingPaneLayout>

 


Repository

1. FirebaseFunctionsRepository

1) 메일 보내기

반려 메일을 보내기 위해선 백엔드 서버로 보내야 해요.  Firebase에서 그 역할은 Functions가 맡고 있기 때문에 해당 기능이 있는 API로 보내면 되겟죠?

API에 맞는 형식의 data를 넘겨받고 API를 호출하여 성공 여부에 따른 콜백 리스너 메소드를 호출해줍니다. 

/**
     * sends a reject mail
     * 반려 메일을 보낸다.
     *
     * @param data     - 이메일 내용 (수신자, 제목, 내용)
     * @param listener - 콜백 리스너 (성공, 실패)
     */
    public void sendRejectEmail(Map<String, Object> data, SendRejectEmailListener listener) {
        // function http 콜을 위한 이름
        String functionName = "sendEmail";
        mFunctions.getHttpsCallable(functionName)
                .call(data)
                .addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
                        listener.onSuccess();
                    } else {
                        listener.onError();
                    }
                });
    }

2) 메일 형식 맞추기

API일 경우 형식을 맞쳐 줘야합니다. 그래서 백엔드 서버에 맞는 형식으로 Map에 넣어서 메일 내용을 감싸줍니다. 

/**
     * 이메일 데이터 포매터
     * 데이터를 이메일 파라미터로 연결 시킨다
     *
     * @param to      - 수신자
     * @param subject - 제목
     * @param text    - 내용
     * @return => Map에 포맷된 내용
     */
    public Map<String, Object> formatEmail(String to, String subject, String text) {
        Map<String, Object> data = new HashMap<>();
        data.put("to", to);
        data.put("subject", subject);
        data.put("text", text);
        return data;
    }

ViewModel

1. RejectViewModel

1) base

거부 메시지를 보내는 것과 거부 메시지를 받는 ViewModel입니다.

거부 사유, 거부 사유 검증한 결과, 거뷰 사유 선택 했는지 확인, 사유를 메일로 보낸 결과를 저장할 LiveData들이 있네요.

/**
 * 거부 내용 선택 및 원하는 거부 내용을 전달하는 ViewModel
 */
public class RejectViewModel extends ViewModel {
    private final FirebaseFunctionsRepository functions;
    /**
     * 거부 사유
     */
    private final MutableLiveData<String> rejectReason;
    /**
     * 거부 사유 포맷 검증 결과
     */
    private final MutableLiveData<Boolean> validRejectReasonResult;
    /**
     * 거부 사유 선택
     */
    private final MutableLiveData<Boolean> rejectOptionChecked;
    /**
     * 거부 사유를 이메일로 전송한 결과
     */
    private final MutableLiveData<Boolean> sendEmailResult;

    public RejectViewModel() {
        functions = GlobalApplication.getFirebaseFunctionsRepository();
        rejectReason = new MutableLiveData<>();
        validRejectReasonResult = new MutableLiveData<>();
        rejectOptionChecked = new MutableLiveData<>();
        sendEmailResult = new MutableLiveData<>();
    }

}

2) 거부 메일 보내기

메일에 형식에 맞게 데이터를 가공하여 repository에게 넘겨서 처리합니다.

메일 제목은 임의로 정했습니다.

콜백으로 받는 결과를 view에 전달하기 위해 sendEmailResult에 저장을 했습니다.

    /**
     * 거부 메시지 전달하기
     * mail format에 맞춰 wrapping하여 전송
     * 거부 사유는 rejectReason에서 가져 옴
     * title - 보내는 메일의 제목
     *
     * @param email - 보낼 상대의 email
     */
    public void sendRejectEmail(String email) {
        // 보내는 메일의 제목
        String title = "거부 메시지 제목";

        functions.sendRejectEmail(functions.formatEmail(email, title, this.rejectReason.getValue()), new SendRejectEmailListener() {
            @Override
            public void onSuccess() {
                sendEmailResult.postValue(true);
            }

            @Override
            public void onError() {
                sendEmailResult.postValue(false);
            }
        });
    }

3) 거부 사유 내용 검증

입력한 내용이 공백이 아닌지, 비어있는지만 검사 했습니다.

view에 전달하기 위해 isValidRejectReason

    /**
     * 거부 사유 포맷 검증
     * 입력 받는 거부 사유가 비었는지 만 확인
     *
     * @param reason - 거부 사유 내용
     */
    public void isValidRejectReason(String reason) {
        if (reason.trim().isEmpty()) {
            isValidRejectReason.setValue(false);
        } else {
            isValidRejectReason.setValue(true);
        }
    }

Getter와 Setter는 생략했습니다.

 

Activity or Fragment

거부 메시지를 보낼 수 있는 view를 사용하는 View 중 하나를 선택했어요.

1.ContractPartnerFragment

1) base

view binding와 view Model을 field에 선언줍니다.

{
	...
    
    /**
     * 거부 메일 보내는 view binding
     */
    private ComponentRejectSelectBinding mRejectBinding;
    /**
     * 거부 메일 보내는 view Model
     */
    private RejectViewModel mRejectViewModel;
    
    ...
    
}

2) 거부 메일 view 초기화

main view와 거부 메일  view와 상호 작용할 부분의 view 가져와서 이벤트 리스너를 추가 해줍니다.

 private void initRejectSelectView() {
        // init
        mRejectViewModel = new ViewModelProvider(this).get(RejectViewModel.class);
        mRejectBinding = mMainBinding.includedRejectSelectView;

        // get main view
        final Button rejectButton = mMainBinding.resultButton;
        final ConstraintLayout rootLayout = mMainBinding.getRoot();


        // get reject view
        final RadioGroup rejectOptions = mRejectBinding.infoFormRejectRadioGroup;
        final SlidingPaneLayout slidingPanel = mRejectBinding.getRoot();
        final Button rejectAcceptButton = mRejectBinding.infoFormRejectAcceptButton;
        final EditText rejectReasonEditText = mRejectBinding.infoFormRejectReasonEditText;
		...
    }

거부 옵션에 따라 이벤트를 추가 해줍니다.

        // 옵션을 선택 했으면, 보내는 버튼 활성화
        mRejectViewModel.getRejectOptionChecked().observe(getViewLifecycleOwner(), aBoolean -> {
                    rejectAcceptButton.setEnabled(aBoolean);
                }
        );
        // 보낸 메일 성공 여부에 따라 메시지 출력
        mRejectViewModel.getSendEmailResult().observe(getViewLifecycleOwner(), aBoolean -> {
            if (aBoolean) { // 성공 시
                showSendEmailSuccess();
                // 1초뒤 이전 화면으로 돌아 가고, 이 프래그먼트 종료
                exitFragment();
            } else { // 실패 시
                showSendEmailFail();
            }
        });
        rejectButton.setVisibility(View.VISIBLE);
        rejectReasonEditText.setEnabled(false);

        // 거부 메일을 보내기 위한 view 보여주기
        rejectButton.setOnClickListener(view -> {
            slidingPanel.setVisibility(View.VISIBLE);
            // 애니메이션 추가
            Animator animator = AnimatorInflater.loadAnimator(getContext(), R.animator.upper_apearing_animator);
            animator.setTarget(slidingPanel);
            animator.start();
            mMainBinding.includedRejectSelectView.getRoot().setVisibility(View.VISIBLE);

            rootLayout.setOnClickListener(v -> {
                if (slidingPanel.getVisibility() == View.VISIBLE) {
                    slidingPanel.setVisibility(View.INVISIBLE);
                }
            });
        });

        // 거부 선택 옵션에 따라 거부 메시지 설정
        rejectOptions.setOnCheckedChangeListener((group, id) -> {
            switch (id) {
                case (R.id.infoFormRejectRadioOption1): {
                    mRejectViewModel.isValidRejectReason(getString(R.string.info_form_reject_reason_option_1));
                    mRejectViewModel.setRejectReason(getString(R.string.info_form_reject_reason_option_1));
                    break;
                }
                case (R.id.infoFormRejectRadioOption2): {
                    mRejectViewModel.isValidRejectReason(getString(R.string.info_form_reject_reason_option_2));
                    mRejectViewModel.setRejectReason(getString(R.string.info_form_reject_reason_option_2));
                    break;
                }
                case (R.id.infoFormRejectRadioOption3): {
                    mRejectViewModel.isValidRejectReason(getString(R.string.info_form_reject_reason_option_3));
                    mRejectViewModel.setRejectReason(getString(R.string.info_form_reject_reason_option_3));
                    break;
                }
                case (R.id.infoFormRejectRadioOption4): {
                    rejectReasonEditText.setEnabled(true);
                    mRejectViewModel.isValidRejectReason(rejectReasonEditText.getText().toString());
                    if (mRejectViewModel.isValidRejectReasonResult()) {
                        mRejectViewModel.setRejectReason(rejectReasonEditText.getText().toString());
                    }
                    break;
                }
                default: {
                    break;
                }
            }
            boolean checked = id != -1;
            mRejectViewModel.setRejectOptionChecked(checked);
        });
        
        // 거부 메일 내용을 변경 시 감시 하여 형식에 맞는지 확인
        TextWatcher textWatcher = new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                //ignore
            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                //ignore
            }

            @Override
            public void afterTextChanged(Editable editable) {
                mRejectViewModel.isValidRejectReason(rejectReasonEditText.getText().toString());
                rejectAcceptButton.setEnabled(mRejectViewModel.isValidRejectReasonResult());
            }
        };

        rejectReasonEditText.addTextChangedListener(textWatcher);
        // 거부 메일 보내는 버튼 클릭 시 거부 메일 보내기
        rejectAcceptButton.setOnClickListener(view -> {
            if (rejectOptions.getCheckedRadioButtonId() == R.id.infoFormRejectRadioOption4 && !mRejectViewModel.isValidRejectReasonResult()) {
                warningEmptyEditText();
            } else {
                mRejectViewModel.sendRejectEmail(mMainViewModel.getTargetCompany().getEmail());
            }
        });

참고용으로 view에 애니메이션을 추가하기 위한 upper_aperaring_animator.xml입니다.
위쪽으로 0.5동안 움직이는 내용이 있습니다.

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:valueFrom="1000"
        android:valueTo="0"
        android:propertyName="translationY"
        android:duration="500"
        />
</set>

이상으로 안드로이드에서 메일을 보내는 법에 대해 알아 보았습니다.
서버 쪽으로 전달할 때 format을 맞춰야 한다는 점과 View-ViewModel-Model 간에 연결이 잘 되도록 설정하는 것이 중요하겠죠?

 

 

 

 

728x90
반응형