Android에서 Firebase 로그인 - 2(Repository, Layout)

2023. 5. 3. 21:46Mobile/Android

작성자알 수 없는 사용자

728x90
반응형

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

이번에는 Android에서 Firebase와 통신을 위한 Repository와 화면을 위한 Layout까지 다뤄 볼게요. 

이번 포스트에는 Local 환경에 firebase emulator와 통신하는 거니 참고하세요.

안드로이드는 Firebase와 미리 연동을 시켜 놨습니다.


Denpendency

안드로이드에서 Firebase를 쓰기 위해서는 Dependency가 필요해요.  밑에 코드를 build.gradle에 넣어주고 sync를 해주세요.

dependencies{
	...
    // for firebase
    // Import the Firebase BoM
    implementation platform('com.google.firebase:firebase-bom:31.3.0')
    implementation 'com.google.firebase:firebase-analytics'
    implementation 'com.google.firebase:firebase-auth'
    implementation 'com.google.firebase:firebase-functions'
    implementation 'com.google.firebase:firebase-storage'
    implementation 'com.google.firebase:firebase-firestore'
    ...
}

1. Repository

1)FirebaseAuthRepository

토큰을 받아오기전 로그인이 되어 있어야지 사용자가 인증이 되겠죠?  그와 관련된 Repository 클래스를 작성해 봅시다!

FirebaseAuth와 통신을 위해 해당 객체를 Firebase에서 제공하는 객체로 가져옵니다. FirebaseFunction에서 커스텀 토큰을 받아와야하니 해당 Repository도 가져옵니다. 싱글톤으로 쓸것이기 때문에 getInstance()를 통해 하나의 객체만 생성되도록 합니다.

/**
 * Firebae Auth를 위한 Repository 싱글톤
 */
public class FirebaseAuthRepository {

    /** 싱글톤 인스턴스 */
    private static volatile FirebaseAuthRepository sSingleton;
    /** Firebase Auth 객체 */
    protected FirebaseAuth mAuth;
    /** Firabse Functions Repository 객체 */
    private final FirebaseFunctionsRepository mFunctions;
    /**
     * noArgs 생성자
     * Firebase Auth 가져 오기
     * Firebase Function Repository 가져 오기
     */
    public FirebaseAuthRepository() {
        mAuth = FirebaseAuth.getInstance();
        mFunctions = GlobalApplication.getFirebaseFunctionsRepository();
    }

    /**
     * 싱글 톤을 위한 인스턴스 반환
     * @return - FirebaseAuthRepository
     */
    public static FirebaseAuthRepository getInstance() {
        if (sSingleton == null) {
            sSingleton = new FirebaseAuthRepository();
        }
        return sSingleton;
    }
}

1. 회원가입

로그인을 하기 전에 계정을 먼저 등록을 해야겟죠?
이메일과 비밀번호를 받아 Auth에서 계정을 생성하고 Functions와 통신해서 커스텀 룰을 적용 시키는 메소드입니다.

 /**
     * 인가가 포함된 회원가입
     * 이메일과 패스워드를 받아 backend server 보내 특정한 인가가 포함된 계정을 생성하도록 프로세스가 진행된다
     * @param email - 이메일
     * @param password - 비밀번호
     * @param listener - 계정 생성 성공 및 실패에 대한 처리를 담은 리스너
     */
    public void registryAuthorization(String email, String password, RegistryListener listener) {
        Log.d("create_user", "start create User");
        mAuth.createUserWithEmailAndPassword(email, password)
                .addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
                        String role = "admin";
                        mFunctions.setUserRole(mAuth.getUid(), role, new FirebaseFunctionsRepository.SetUserRoleListener() {
                            @Override
                            public void onSuccess() {
                                listener.onSuccess();
                            }

                            @Override
                            public void onError() {

                            }
                        });
                    } else {
                        Objects.requireNonNull(task.getException()).printStackTrace();
                        listener.onError();
                    }

                });
    }

2. 인가 정보 조회

FirebaseAuth에서 CustomClaims를 사용자가 추가할 수 있어요.
인증된 사용자로부터 Custom Claim을 가져오는 메소드 다음과 같습니다. 
로그인된 사용자로부터 토큰을 가지고 와서, 토큰 안에 있는 Custom claims를 꺼내서 인자로 받은 콜백 리스너에게 전달합니다.

/**
     * 인가 정보를 조회
     * 로그인되어 있는 계정이 있으면 그 계정에 있는 토큰 안에 있는 인가 정보를 가져 온다.
     */
    private void showClaims(ShowClaimListener listener) {
        FirebaseUser user = mAuth.getCurrentUser();
        if (user != null) {
            user.getIdToken(true)
                    .addOnCompleteListener(task -> {
                        if (task.isSuccessful()) {
                            Map<String, Object> claims = task.getResult().getClaims();
                            String customClaim = (String) claims.get("role");
                            Log.d("role", Objects.requireNonNullElse(customClaim, "null role"));
                            listener.onSuccess(claims);
                        }
                    });
        }
    }
    
    // 각 메소드 실행 후 결과에 대해 처리할 process를 넣을 콜백 인터페이스
    private interface ShowClaimListener {
        void onSuccess(Map<String, Object> claims);
        void onError();
    }
    ...

2)FirebaseFunctionsRepository

FirebaseFuctions와 통신을 위해 해당 객체를 Firebase에서 제공하는 객체로 가져옵니다. 싱글톤으로 쓸것이기 때문에 getInstance()를 통해 하나의 객체만 생성되도록 합니다.

/**
 * Firebase Functions를 위한 Repository 싱글톤
 */
public class FirebaseFunctionsRepository {
    /**
     * 싱글톤 인스턴스
     */
    private static volatile FirebaseFunctionsRepository sSingleton;
    /**
     * Firebase Function 객체
     */
    protected FirebaseFunctions mFunctions;

    /**
     * no Args 생성자
     * Functions Functions 객체 가져 오기
     */
    public FirebaseFunctionsRepository() {
        mFunctions = FirebaseFunctions.getInstance();
    }

    /**
     * 싱글톤 객체 반환
     *
     * @return - FirbaseFunctionsRepository
     */
    public static FirebaseFunctionsRepository getInstance() {
        if (sSingleton == null) {
            sSingleton = new FirebaseFunctionsRepository();
        }
        return sSingleton;
    }

   ...
}

1. 커스텀 토큰 발급

커스텀 토큰을 가져오기 위해서는 사용자 UID가 필요해요. 인자로 받아 들이고, 통신의 결과를 콜백할 리스너도 받아요.
Functions에 uid를 전달하여 리턴 받은 데이터에서 토큰을 콜백 리스너에 다시 전달하는 메소드이네요.

 /**
     * custom Token 발급
     * backend server에 uid을 전달하여 custom Token을 가져 오는 메소드
     *
     * @param uid      - 사용자의 uid
     * @param listener - 콜백 리스너(성공 시 토큰 전달, 실패)
     */
    public void getCustomToken(String uid, CustomTokenListener listener) {
        // 커스텀 토큰을 생성하는 HTTPS Callable Function 이름
        String functionName = "createCustomToken";
        // HTTPS Callable Function에 전달할 파라미터
        Map<String, Object> data = new HashMap<>();
        data.put("uid", uid);

        // 커스텀 토큰을 생성하는 HTTPS Callable Function 호출
        mFunctions.getHttpsCallable(functionName)
                .call(data)
                .addOnCompleteListener(task -> {
                    String tag = "custom token";

                    if (task.isSuccessful()) {
                        // HTTPS Callable Function 호출이 성공한 경우
                        Object result = task.getResult().getData();
                        if (result instanceof Map) {
                            String customToken = (String) ((Map) result).get("token");
                            // 받아온 커스텀 토큰을 사용하여 로그인 처리 등을 수행할 수 있습니다.
                            Log.d(tag, " custom token success " + customToken);
                            listener.onSuccess(customToken);
                        }

                    } else {
                        Log.d(tag, task.getException().toString());
                        Log.d(tag, "custom token Fail");
                    }
                });

    }

2. 유저 인가 할당

AuthRepository에서 customClamis를 적용 시키기위해서는 Functions에서 관여를 해줘야 해요. 

유저의 UID와 설정할 역할을 Functions에 넘기고 결과를 콜백 리스너에게 전달하는 메소드이에요.

 /**
     * set user role
     * 받아온 역할으로 해당 사용자의 역할을 설정 한다.
     *
     * @param uid      - 유저 계정 UID
     * @param role     - 유저 설정할 역할
     * @param listener - 콜백 리스너 (성공, 실패)
     */
    protected void setUserRole(String uid, String role, SetUserRoleListener listener) {
        // function http 콜을 위한 이름
        String functionName = "setCustomUserRole";
        // 전달할 데이터 포매팅
        Map<String, Object> data = new HashMap<>();
        data.put("uid", uid);
        data.put("role", role);
        mFunctions.getHttpsCallable(functionName)
                .call(data)
                .addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
                        listener.onSuccess();
                    } else {
                        task.getException().printStackTrace();
                        listener.onError();
                    }
                });
    }

2. Layout

1)activity_login.xml

이제는 로그인하는 화면이 필요하겟죠? 

이 화면에는 로그인, 회원가입, 구글로그인, 네이버로그인, 카카오 로그인까지 있는 화면 이지만, 로그인과 회원가입만 쓰는걸로 진행할게요.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".ui.login.LoginActivity">

    <EditText
        android:id="@+id/username"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="96dp"
        android:autofillHints="@string/prompt_email"
        android:hint="@string/prompt_email"
        android:inputType="textEmailAddress"
        android:selectAllOnFocus="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/password"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:autofillHints="@string/prompt_password"
        android:hint="@string/prompt_password"
        android:imeActionLabel="@string/action_sign_in_short"
        android:imeOptions="actionDone"
        android:inputType="textPassword"
        android:selectAllOnFocus="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/username" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@+id/password"
        android:orientation="vertical"
        android:layout_gravity="center_horizontal"
        >

        <LinearLayout
            android:id="@+id/button_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="16dp"
            android:gravity="center_horizontal"
            >
            <Button
                android:id="@+id/login"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:enabled="false"
                android:layout_marginRight="15pt"
                android:text="@string/action_sign_in"
            />
            <Button
                android:id="@+id/registry"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginLeft="15pt"
                android:enabled="false"
                android:text="@string/action_registry"
            />
        </LinearLayout>
    <com.google.android.gms.common.SignInButton
        android:id="@+id/googleSignInButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="16pt"
        />

    <com.navercorp.nid.oauth.view.NidOAuthLoginButton
        android:id="@+id/naverAuthLoginButton"
        android:layout_width="wrap_content"
        android:layout_height="65dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="16pt"
        />
    <ImageButton
        android:id="@+id/kakaoLoginButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginBottom="40dp"
        android:background="@android:color/transparent"
        android:src="@drawable/kakao_login_medium_wide"
        android:layout_marginTop="16pt"
        >
    </ImageButton>

    </LinearLayout>

    <ProgressBar
        android:id="@+id/loading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="64dp"
        android:layout_marginBottom="64dp"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/password"
        app:layout_constraintStart_toStartOf="@+id/password"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.3" />

</androidx.constraintlayout.widget.ConstraintLayout>

여기까지 하고 다음 포스트에서 Activity와 ViewModel에 대해 다뤄 보도록 하죠!

728x90
반응형