갤럭시 워치에 버스 도착 정보를 띄우자!(5) - Firebase에 데이터 저장하기

김흰돌·2023년 7월 17일
0

이번엔 Firebase를 이용하여 Serverless 하게 데이터를 저장하는 방법을 알아보려고 한다.
내 프로젝트와 Firebase를 연동하는 방법을 담은 글들은 많으니 바로 코드로 넘어가려고 한다.

시작하기 전에 Firebase에서 제공하는 서비스 중 Authentication과 Firestore Database를 사용할 예정이다.

내 버스 도착 정보를 저장하기 위해선 User를 구분해야 하기 때문에 Authentication을 통해 회원가입과 로그인을 진행하고, 데이터를 저장하기 위해 Firestore Database를 사용한다.

이 프로젝트에선 Hilt와 Repository Pattern을 사용하니 관련 코드를 먼저 작성한다.


FirebaseModule

@Module
@InstallIn(SingletonComponent::class)
object FirebaseModule {

    @Provides
    @Singleton
    fun provideFirebaseAuthInstance(): FirebaseAuth {
        return FirebaseAuth.getInstance()
    }

    @Provides
    @Singleton
    fun provideFireStoreInstance(): FirebaseFirestore {
        return FirebaseFirestore.getInstance()
    }
}
  • @Module 어노테이션은 Dagger Hilt에 의해 사용되며, 모듈로 지정된 클래스를 의존성 그래프에 포함시키는 역할을 한다.
  • @InstallIn(SingletonComponent::class) 어노테이션은 이 모듈이 어떤 컴포넌트에 설치될지를 지정한다. 위의 코드에서는 SingletonComponent에 설치하도록 지정되어 있으므로, 앱의 전역 범위에서 공유되는 싱글톤 객체로 관리됨
  • @Provides 어노테이션은 해당 메서드가 의존성 객체를 제공하는 역할을 한다는 것을 Dagger Hilt에 알려주는 역할을 한다.
  • @Singleton 어노테이션은 해당 객체가 싱글톤으로 관리되어야 함을 나타낸다.

Repository

interface FirebaseRepository {

    suspend fun signIn(email: String, passwd: String): Boolean

    suspend fun createAccount(email: String, passwd: String): Boolean

    suspend fun insertMyBookmark(stationID: String, routeID: String, routeNm: String): Boolean

    suspend fun getMyBookmark(): QuerySnapshot

    suspend fun deleteBookmark(busNm: String): Boolean

}
class FirebaseRepositoryImpl @Inject constructor(
    private val auth: FirebaseAuth,
    private val db: FirebaseFirestore,
    private val imageStorage: FirebaseStorage
) : FirebaseRepository {

    private val currentTime: Long = System.currentTimeMillis()
    private val dateFormat = SimpleDateFormat("yy-MM-dd_HH:mm:ss")
    
    override suspend fun signIn(email: String, passwd: String): Boolean {
        return try {
            val authResult = auth.signInWithEmailAndPassword(email, passwd).await()
            (authResult.user != null)
        } catch (e: Exception) {
            false
        }
    }

    override suspend fun createAccount(email: String, passwd: String): Boolean {
        return try {
            val authResult = auth.createUserWithEmailAndPassword(email, passwd).await()
            (authResult.user != null)
        } catch (e: Exception) {
            false
        }
    }

    override suspend fun insertMyBookmark(
        stationID: String,
        routeID: String,
        routeNm: String
    ): Boolean {
        return try {
            val bookmarkEntity = BookmarkEntity(
                stationID = stationID,
                routeID = routeID,
                routeNm = routeNm
            )
            auth.currentUser?.let {
                db.collection(it.uid)
                    .document(dateFormat.format(currentTime))
                    .set(bookmarkEntity).await()
            }
            true
        } catch (e: Exception) {
            false
        }
    }

    override suspend fun getMyBookmark(): QuerySnapshot {
            val dbResult = auth.currentUser?.let {
                db.collection(it.uid)
                    .get()
                    .await()
            }
        return dbResult!!
    }

    override suspend fun deleteBookmark(busNm: String): Boolean {
        return try {
            val dbResult = auth.currentUser?.let {
                db.collection(it.uid)
                    .whereEqualTo("routeNm", busNm)
                    .get()
                    .await()
            }
            if (dbResult != null) {
                for (result in dbResult) {
                    result.reference.delete()
                }
            }
            true
        } catch (e: Exception) {
            false
        }
    }
}
  • 로그인, 회원가입, 데이터 저장, 데이터 조회, 데이터 삭제를 수행하는 함수
  • Firebase에서 친절하게 코드를 작성하는 예시를 보여주기 때문에 아마 Firebase를 사용하는 사람들은 전부 비슷하게 작성했을 것 같다.
  • Coroutine을 사용하기 위해 suspend가 붙어있다.
  • 데이터를 저장할 때의 시간을 기록하기 위해 currentTime, dataFormat 변수를 작성했다.

ViewModel에서 데이터 처리

ViewModel의 목적에 따라 Repository에 작성한 함수를 사용하면 된다.
위에서 작성한 fireBaseRepositoryImpl를 주입해주면 된다.

@HiltViewModel
class BusArrivalViewModel @Inject constructor(
    private val firebaseRepository: FirebaseRepositoryImpl
) : ViewModel(){

    private val _insertResult = MutableLiveData<Boolean>()
    val insertResult: LiveData<Boolean>
        get() = _insertResult

    fun insertMyBookmark(stationID: String, routeID: String, routeNm: String) {
        viewModelScope.launch {
            try {
                _insertResult.value = firebaseRepository.insertMyBookmark(stationID, routeID, routeNm)
            } catch (e: Exception) {
                Log.e("InsertBookmarkErr", e.toString())
            }
        }
    }
}
  • @HiltViewModel 어노테이션은 Dagger Hilt에서 제공하는 ViewModel 관련 어노테이션으로, ViewModel 클래스에 추가하여 Dagger Hilt가 ViewModel을 생성하고 의존성 주입을 처리하도록 한다.
  • @Inject 생성자: ViewModel 클래스의 생성자에 @Inject 어노테이션을 추가하여 Dagger Hilt에 의해 의존성 주입 대상임을 알려줌

이렇게 미리 Repository에 사용할 함수만 작성해 놓으면 원하는 ViewModel에서 아주 간단하게 Firebase가 제공하는 서비스들을 사용할 수가 있다.


갤럭시 워치에 버스 도착 정보를 띄우자! 시리즈를 처음부터 지금까지 따라왔다면 일단 스마트폰 용 앱은 완성이 됐다.(사실 아직 불안정하고 UI도 보기 좋지 않아서 많은 보완을 해야한다...)

버스언제와 Git
다음은 이 앱을 통해 즐겨찾기에 추가한 내 버스 도착 정보를 갤럭시 워치에서 확인할 수 있는 갤럭시 워치 앱을 만드는 법을 포스팅할 예정이다.

1개의 댓글

comment-user-thumbnail
2023년 7월 17일

덕분에 좋은 정보 얻어갑니다, 감사합니다.

답글 달기