PROJECT๐Ÿ’ป

[Android] Green Market ํ”„๋กœ์ ํŠธ

์ด๋ฆฌ์ญ 2025. 2. 10. 21:54

ํ•ด๋‹น ํ”„๋กœ์ ํŠธ GitHub Repository

https://github.com/ssarisong/Green-Market

 

GitHub - ssarisong/Green-Market: 2023๋…„ ํ•œ์„ฑ๋Œ€ํ•™๊ต ๊ณ ๊ธ‰๋ชจ๋ฐ”์ผํ”„๋กœ๊ทธ๋ž˜๋ฐ ํŒ€ ํ”„๋กœ์ ํŠธ

2023๋…„ ํ•œ์„ฑ๋Œ€ํ•™๊ต ๊ณ ๊ธ‰๋ชจ๋ฐ”์ผํ”„๋กœ๊ทธ๋ž˜๋ฐ ํŒ€ ํ”„๋กœ์ ํŠธ. Contribute to ssarisong/Green-Market development by creating an account on GitHub.

github.com

 

1. ์ฃผ์ œ ์„ค๋ช…

 ํ•œ์„ฑ๋Œ€ํ•™๊ต ๊ณ ๊ธ‰๋ชจ๋ฐ”์ผํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ณผ๋ชฉ ๊ธฐ๋ง ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค.

๊ณ ๊ธ‰๋ชจ๋ฐ”์ผํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ˆ˜์—…์—์„œ ๋ฐฐ์šด ๊ธฐ๋Šฅ๋“ค์„ ์ตœ๋Œ€ํ•œ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๊ธฐ๋Šฅ์˜ ์ด์ง‘ํ•ฉ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋Š” ์ค‘๊ณ ๋งˆ์ผ“์„ ์ฃผ์ œ๋กœ ์„ ์ •ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.


2. ๊ฐœ๋ฐœ ๊ธฐ๊ฐ„

2023.08.24 ~ 2023.12.02 (์•ฝ 3๊ฐœ์›”)


3. ์—ญํ• 

ํŒ€์žฅ, ๋ฐฑ์—”๋“œ ์ด๊ด„
  • ์„ค๊ณ„
    • FireStore ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„
    • ๋กœ๊ทธ์ธ ๊ณผ์ • ์„ค๊ณ„
    • Storage์— ์ ์žฌ๋  Resource ๊ตฌ์กฐ ์„ค๊ณ„
  • ๊ตฌํ˜„
    • ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„
    • ์ค‘๊ณ ๋งˆ์ผ“ ๊ฒŒ์‹œ๊ธ€ CRUD ๊ตฌํ˜„
    • ์ฑ„ํŒ… ๊ธฐ๋Šฅ ๊ตฌํ˜„
  • ์—ฐ๊ฒฐ
    • ๊ฐœ๋ฐœ๋œ Util ๊ธฐ๋Šฅ UI์— ์—ฐ๊ฒฐ

4. ๊ธฐ์ˆ  ์Šคํƒ

  • ์–ธ์–ด: Kotlin
  • ํ”„๋ ˆ์ž„์›Œํฌ: Android
  • BaaS: Firebase
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: Firestore
  • ๋ฌธ์„œํ™”: Dokka

5. ์ „์ฒด ๊ตฌ์กฐ

 

 


6. ์ƒ์„ธ ๊ตฌํ˜„ ๋‚ด์šฉ

BaaS ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐฑ์—”๋“œ ๊ธฐ๋Šฅ์„ ๋”ฐ๋กœ Api๋กœ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•˜๊ณ , Util ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด UI์—์„œ ์‚ฌ์šฉํ•  ๊ธฐ๋Šฅ์„ ํ•จ์ˆ˜๋กœ ํ‘œํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

Callback ํ•จ์ˆ˜๋ฅผ ์ฃผ๋กœ ์‚ฌ์šฉํ•œ ์ด์œ 
Firebase์—์„œ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ๋‹ค์‹œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ๋Œ์•„์˜ค๋Š” ๊ณผ์ •์—์„œ ๋น„๋™๊ธฐ ์ž‘์—…์ด ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์— Callback ํ•จ์ˆ˜๋กœ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

6-1. ๋กœ๊ทธ์ธ

Firebase์—์„œ Authentication ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋กœ๊ทธ์ธ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ์ด๋ฉ”์ผ / ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ฐฉ๋ฒ•์„ ์ฑ„ํƒํ–ˆ๊ณ , Firestore์—๋„ ํšŒ์›์ •๋ณด๋ฅผ ์ €์žฅํ–ˆ์Šต๋‹ˆ๋‹ค.

Firestore์— ํšŒ์›์ •๋ณด๋ฅผ ์ €์žฅํ•œ ์ด์œ 
Authentication์—๋Š” ์‚ฌ์šฉ์ž์˜ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ๋งŒ ์ €์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€์ ์ธ ์‚ฌ์šฉ์ž ๊ด€๋ จ ์ •๋ณด๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.
์ด ๋•Œ๋ฌธ์— Firestore ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•˜์˜€๊ณ , ์‚ฌ์šฉ์ž ์ •๋ณด์ธ ์ƒ๋…„์›”์ผ ๋“ฑ์˜ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

[ํšŒ์›๊ฐ€์ž… ๊ตฌํ˜„]

fun doSignUp(userEmail: String, password: String, name: String, birth: String, callback: (Int, String?) -> Unit){
        Firebase.auth.createUserWithEmailAndPassword(userEmail, password)
            .addOnCompleteListener{additionTask ->
                if(additionTask.isSuccessful){
                    val uid = Firebase.auth.currentUser?.uid.toString()
                    val currentUser = Firebase.auth.currentUser
                    val parser = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
                    val birthDate = Timestamp(parser.parse(birth))
                    val newUser = User(userEmail, name, birthDate, Timestamp.now())
                    userModel.insertUser(uid, newUser) { STATUS_CODE ->
                        if (STATUS_CODE == StatusCode.SUCCESS) {
                            callback(StatusCode.SUCCESS, uid)
                        } else {
                            currentUser?.delete()
                                ?.addOnCompleteListener { deletionTask ->
                                    if (deletionTask.isSuccessful) {
                                        Log.d("FirebaseLoginUtils", "[${uid}] ๊ณ„์ • DB ์ •๋ณด ์ถ”๊ฐ€ ์‹คํŒจ๋กœ ์ธํ•œ Auth ๊ณ„์ • ์‚ญ์ œ ์„ฑ๊ณต")
                                    } else {
                                        Log.w("FirebaseLoginUtils", "[${uid}] ๊ณ„์ • DB ์ •๋ณด ์ถ”๊ฐ€ ์‹คํŒจ๋กœ ์ธํ•œ Auth ๊ณ„์ • ์‚ญ์ œ ์‹คํŒจ!!! -> ", deletionTask.exception)
                                    }
                                }
                            callback(StatusCode.FAILURE, null)
                        }
                    }
                }else{
                    Log.w("FirebaseLoginUtils", "Auth์—์„œ ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ!!! -> ", additionTask.exception)
                    callback(StatusCode.FAILURE, null)
                }
            }
    }

 

[ํšŒ์›์ •๋ณด Firebase์— ์ €์žฅ]

fun insertUser(uid: String, user: User, callback: (Int) -> Unit) {
        db.collection("User").document(uid).set(user)
            .addOnSuccessListener {
                Log.d("FirestoreUserModel", "[${uid}]:์‚ฌ์šฉ์ž๋ช…[${user.name}]:์‚ฌ์šฉ์ž์ด๋ฉ”์ผ[(${user.email})] ์‚ฌ์šฉ์ž DB ์ •๋ณด ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ")
                callback(StatusCode.SUCCESS)
            }
            .addOnFailureListener { e ->
                Log.w("FirestoreUserModel", "[${uid}] ์‚ฌ์šฉ์ž DB ์ •๋ณด ์ƒ์„ฑ ์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ!!! -> ", e)
                callback(StatusCode.FAILURE)
            }
    }

6-2. ์ค‘๊ณ ๋งˆ์ผ“ ๊ฒŒ์‹œ๊ธ€

์ค‘๊ณ ๋งˆ์ผ“ ๊ฒŒ์‹œ๊ธ€์€ Firestore๋ฅผ ํ†ตํ•ด CRUD ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ค‘๊ณ ๋ฌผํ’ˆ ์ด๋ฏธ์ง€๊ฐ€ ์ฒจ๋ถ€ ๊ฐ€๋Šฅํ•ด์•ผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, Firebase์— Storage ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๊ด€๋ จ ์˜ˆ์™ธ์ฒ˜๋ฆฌ ๋ฐฉ์‹
์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œ ํ•˜๊ฑฐ๋‚˜ ๊ฐ€์ ธ์˜ค๋Š” ๊ณผ์ •์—์„œ ์—ฌ๋Ÿฌ ์ด์œ ๋กœ ์˜ˆ์™ธ ์‚ฌํ•ญ์ด ๋ฐœ์ƒํ•  ์ผ์ด ๋งŽ์•˜์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด addOnFailureListener{}๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์˜ˆ์™ธ ์‚ฌํ•ญ์„ ๋กœ๊ทธ๋กœ ์ „๋‹ฌํ•˜๊ณ , StatusCode๋ฅผ ๋งŒ๋“ค์–ด์„œ http์˜ ์ƒํƒœ์ฝ”๋“œ์ฒ˜๋Ÿผ ์ž‘๋™ํ•˜๋„๋ก ํ•˜์—ฌ Callback์œผ๋กœ return ํ–ˆ์Šต๋‹ˆ๋‹ค.
์ด ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐ๋Šฅ ์ค‘ ์˜ˆ์™ธ ์‚ฌํ•ญ์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ์–ด๋–ค ์ด์œ ์˜ ์—๋Ÿฌ์ธ์ง€ ํŒŒ์•…ํ•˜๊ธฐ ์‰ฌ์› ์Šต๋‹ˆ๋‹ค.

 

[์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๊ณผ์ • - ์ค‘๊ณ ๋ฌผํ’ˆ ์ถ”๊ฐ€ ๊ณผ์ • ์ค‘]

...
	imageRef.putFile(Uri.parse(product.img))
            .addOnSuccessListener {
                imageRef.downloadUrl.addOnSuccessListener { uri ->
                    val updatedProduct =
                        product.copy(productId = newProduct.id, img = uri.toString())

                    newProduct.set(updatedProduct).addOnSuccessListener {
                        Log.d(
                            "FirestoreProductModel",
                            "([${newProduct.id}]:์ƒํ’ˆ๋ช…[${product.name}] ์ƒํ’ˆ DB ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ"
                        )
                        callback(StatusCode.SUCCESS, newProduct.id)
                    }.addOnFailureListener { e ->
                        Log.w("FirestoreProductModel", "[${newProduct.id}]:์ƒํ’ˆ๋ช…[${product.name}] ์ƒํ’ˆ DB ์ •๋ณด ์ƒ์„ฑ ์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ!!! -> ", e)
                        callback(StatusCode.FAILURE, null)
                    }
                }.addOnFailureListener { e ->
                    Log.w("FirestoreProductModel", "[${newProduct.id}]:์ƒํ’ˆ๋ช…[${product.name}] ์ƒํ’ˆ ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ URL ๋ฐ›๊ธฐ ์‹คํŒจ!!! -> ", e)
                    callback(StatusCode.FAILURE, null)
                }
            }
            .addOnFailureListener { e ->
                Log.w("FirestoreProductModel", "[${newProduct.id}]:์ƒํ’ˆ๋ช…[${product.name}]์ƒํ’ˆ ์ด๋ฏธ์ง€ ์ €์žฅ ์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ!!! -> ", e)
                callback(StatusCode.FAILURE, null)
            }
 ...

 

[ ์ด๋ฏธ์ง€ ์‚ญ์ œ ๊ณผ์ • - ์ค‘๊ณ ๋ฌผํ’ˆ ์‚ญ์ œ ๊ณผ์ • ์ค‘ ]

...
    val imageRef = FirebaseStorage.getInstance().reference.child("Product/${pid}")
        imageRef.metadata
            .addOnSuccessListener {
                imageRef.delete()
                    .addOnSuccessListener {
                        db.collection("Product").document(pid).delete()
                            .addOnSuccessListener {
                                Log.d("FirestoreProductModel", "[${pid}] ์ƒํ’ˆ DB ์ •๋ณด ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ")
                                callback(StatusCode.SUCCESS)
                            }
                            .addOnFailureListener { e ->
                                Log.w("FirestoreProductModel", "[${pid}] ์ƒํ’ˆ DB ์ •๋ณด ์‚ญ์ œ ์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ!!! -> ", e)
                                callback(StatusCode.FAILURE)
                            }
                    } .addOnFailureListener { e ->
                        Log.w("FirestoreProductModel", "[${pid}] ์ƒํ’ˆ ์ด๋ฏธ์ง€ Storage์—์„œ ์‚ญ์ œ ์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ!!! -> ", e)
                        callback(StatusCode.FAILURE)
                    }
            }
            .addOnFailureListener {
                db.collection("Product").document(pid).delete()
                    .addOnSuccessListener {
                        Log.d("FirestoreProductModel", "[${pid}] ์ƒํ’ˆ DB ์ •๋ณด ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ")
                        callback(StatusCode.SUCCESS)
                    }
                    .addOnFailureListener { e ->
                        Log.w("FirestoreProductModel", "[${pid}] ์ƒํ’ˆ DB ์ •๋ณด ์‚ญ์ œ ์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ!!! -> ", e)
                        callback(StatusCode.FAILURE)
                    }
            }
...

 

6-3. ์ฑ„ํŒ…

์ฑ„ํŒ…์€ Firestore Database์— ์˜ํ•ด ๊ด€๋ฆฌ๋˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

NoSQL์ธ Firestore์˜ ํŠน์„ฑ์„ ํ™œ์šฉํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์ด ChatRoom์ด๋ž€ ์ปฌ๋ ‰์…˜์„ ๋งŒ๋“ค๊ณ  ๊ทธ ์•ˆ์—์„œ ์Œ“์ด๋Š” ๋ชจ๋“  ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€๋“ค์„ Chat์ด๋ž€ ํ•˜์œ„ ์ปฌ๋ ‰์…˜์œผ๋กœ ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

 

[์ฑ„ํŒ… DB ๊ตฌ์„ฑ ์˜ˆ์‹œ]

ChatRoom [
	{
        chatRoomId : "3pjzurfyPFkyebOVX6mf"
        productId : "zmaQgjjQLZ5W05ZAjwXf"
        buyerId : "vwyi7dnkKCaH5adeXPk1NrsEdue2"
        sellerId : "kcjEOOK3VtOk0rTpnks8QTPxLbJ2"
        lastMessage : "์•„์ง ์žˆ์Šต๋‹ˆ๋‹ค!"
        lastMessageAt : 2023๋…„ 12์›” 1์ผ ์˜คํ›„ 7์‹œ 33๋ถ„ 59์ดˆ UTC+9
        createdAt : 2023๋…„ 12์›” 1์ผ ์˜คํ›„ 7์‹œ 33๋ถ„ 36์ดˆ UTC+9
        Chat : [
        	{
            	senderId : "vwyi7dnkKCaH5adeXPk1NrsEdue2"
                message : "ํŒ”๋ ธ์„๊นŒ์š”?"
                createdAt : 2023๋…„ 12์›” 1์ผ ์˜คํ›„ 7์‹œ 33๋ถ„ 36์ดˆ UTC+9
            }, 
            {
            	senderId : "kcjEOOK3VtOk0rTpnks8QTPxLbJ2"
                message : "์•„์ง ์žˆ์Šต๋‹ˆ๋‹ค!"
                createdAt : 2023๋…„ 12์›” 1์ผ ์˜คํ›„ 7์‹œ 33๋ถ„ 59์ดˆ UTC+9
            }, 
            ...
        ]
    }, 
    ...
]

 

์ฑ„ํŒ…์—์„œ ๋ฉ”์‹œ์ง€๋Š” ์ƒ๋Œ€๋ฐฉ์ด ๋ณด๋‚ด๋Š” ์ฆ‰์‹œ ํ™•์ธ์ด ๊ฐ€๋Šฅํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด Listener ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜์˜€๊ณ , ์ด๋ฅผ ํ†ตํ•ด ๋ฉ”์‹œ์ง€๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐ์ง€ํ•˜์—ฌ ๋‘ ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐ”๋กœ๋ฐ”๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ, addSnapshotListener{} ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์˜€๋Š”๋ฐ, ์ด๋Š” query๊ฐ€ ๊ฐ์ง€ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ณ  ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ•  ๋•Œ๋งˆ๋‹ค ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

 

[์ฑ„ํŒ…๋ฐฉ ๋‚ด์—์„œ ๋ฉ”์ง€์ง€ ๊ฐ์ง€ Listener]

/**
 * ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์˜ ๋ฉ”์‹œ์ง€๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค.
 *
 * @param chatRoomId ๊ฐ์ง€ํ•  ์ฑ„ํŒ…๋ฐฉ์˜ ID.
 * @param callback ์ƒˆ ๋ฉ”์‹œ์ง€ ๋˜๋Š” ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ์ƒํƒœ ์ฝ”๋“œ(StatusCode)์™€ ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ์„ ์ธ์ž๋กœ ๋ฐ›๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
 * @return ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก์„ ํ•ด์ œํ•˜๋Š” ํ•จ์ˆ˜.
 */
fun listenForMessages(chatRoomId: String, callback: (Int, List<Chat>?) -> Unit): () -> Unit {
    val chatMessagesRef = db.collection("ChatRoom").document(chatRoomId).collection("Chat")
    val query = chatMessagesRef.orderBy("createdAt", Query.Direction.ASCENDING)

    val registration = query.addSnapshotListener { snapshots, e ->
        if (e != null) {
            Log.w("FirestoreChattingModel", "[$chatRoomId] ๋ฉ”์‹œ์ง€ ์‹ค์‹œ๊ฐ„ ๊ฐ์ง€ ์‹คํŒจ!!! -> ", e)
            callback(StatusCode.FAILURE, null)
            return@addSnapshotListener
        }
        if (snapshots != null && !snapshots.isEmpty) {
            val messages = snapshots.mapNotNull { it.toObject(Chat::class.java) }
            Log.d("firestoreChattingModel", "[$chatRoomId] ์ฑ„ํŒ…๋ฐฉ์˜ ์ฑ„ํŒ… ๋ชฉ๋ก ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์™„๋ฃŒ")
            callback(StatusCode.SUCCESS, messages)
        } else {
            Log.d("FirestoreChattingModel", "[$chatRoomId] ๋ฉ”์‹œ์ง€๊ฐ€ ์•„์ง ์—†์Œ")
            callback(StatusCode.SUCCESS, emptyList())
        }
    }
    return { registration.remove() }
}

 

๋˜ํ•œ, ์ฑ„ํŒ…๋ฐฉ ๋ฐ–์— ์‚ฌ์šฉ์ž๊ฐ€ ์œ„์น˜ํ•  ๊ฒฝ์šฐ์— ์ƒˆ๋กœ์šด ์ฑ„ํŒ…์ด ๋ฐœ์ƒํ•˜๋ฉด ๋งˆ์ง€๋ง‰ ์ฑ„ํŒ… ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋งˆ์ง€๋ง‰ ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐ์ง€ํ•˜๋Š” Listener๋„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

[์ฑ„ํŒ…๋ฐฉ ์™ธ์—์„œ ๋ฉ”์‹œ์ง€ ๊ฐ์ง€ Listener]

/**
 * Firestore์˜ ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์— ๋Œ€ํ•œ lastMessage ๋ณ€๊ฒฝ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค.
 *
 * @param chatRoomId ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐ์ง€ํ•  ์ฑ„ํŒ…๋ฐฉ์˜ ID์ž…๋‹ˆ๋‹ค.
 * @param callback lastMessage ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
 */
fun listenForLastMessage(chatRoomId: String, callback: (Int, String?, Timestamp?) -> Unit) {
    db.collection("ChatRoom").document(chatRoomId)
        .addSnapshotListener { snapshot, e ->
            if (e != null) {
                Log.w("FirestoreChatModel", "[${chatRoomId}]์ฑ„ํŒ…๋ฐฉ lastMessage ๋ฆฌ์Šค๋„ˆ ์˜ค๋ฅ˜ ๋ฐœ์ƒ!!! -> ", e)
                return@addSnapshotListener
            }
            if (snapshot != null && snapshot.exists()) {
                Log.d("FirestoreChatModel", "[${chatRoomId}] ์ฑ„ํŒ…๋ฐฉ์˜ ๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€์™€ ๋ณด๋‚ธ ์‹œ๊ฐ„ ์‹ค์‹œ๊ฐ„ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์™„๋ฃŒ")
                callback(StatusCode.SUCCESS, snapshot.getString("lastMessage"), snapshot.getTimestamp("lastMessageAt"))
            } else {
                Log.d("FirestoreChatModel", "[${chatRoomId}] ์ฑ„ํŒ…๋ฐฉ์˜ lastMessage ๋ฐ์ดํ„ฐ ์—†์Œ")
                callback(StatusCode.FAILURE, null, null)
            }
        }
}

7. ์‹คํ–‰ ์‹œ๋‚˜๋ฆฌ์˜ค

7-1. ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ

  • ํšŒ์›๊ฐ€์ž…: ์ด๋ฉ”์ผ, ๋น„๋ฐ€๋ฒˆํ˜ธ, ์ด๋ฆ„, ์ƒ๋…„์›”์ผ ๋ชจ๋‘ ์ž…๋ ฅํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.
  • ๋กœ๊ทธ์ธ: ํšŒ์›๊ฐ€์ž… ํ›„, ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

7-2. ํ™ˆ ํ™”๋ฉด

  • ๋“ฑ๋ก๋˜์–ด ์žˆ๋Š” ์ค‘๊ณ ๋ฌผํ’ˆ ๊ฒŒ์‹œ๊ธ€์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

7-3. ์ค‘๊ณ ๋ฌผํ’ˆ ๊ฒ€์ƒ‰ ํ•„ํ„ฐ

  • ํŒ๋งค์ค‘, ์˜ˆ์•ฝ์ค‘, ํŒ๋งค ์™„๋ฃŒ๋ณ„๋กœ ๋“ฑ๋ก๋˜์–ด ์žˆ๋Š” ์ค‘๊ณ ๋ฌผํ’ˆ ๊ฒŒ์‹œ๊ธ€์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

7-4. ์ค‘๊ณ ๋ฌผํ’ˆ ์ƒ์„ธ ํŽ˜์ด์ง€

  • ๊ฐ๊ฐ์˜ ์ค‘๊ณ ๋ฌผํ’ˆ๋งˆ๋‹ค ์ƒํƒœ ํ˜„ํ™ฉ์„ ๋ณผ ์ˆ˜ ์žˆ๊ณ , '์ฑ„ํŒ…ํ•˜๊ธฐ' ๋ฒ„ํŠผ์„ ํ†ตํ•ด ํŒ๋งค์ž์™€ ์ฑ„ํŒ…์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

7-5. ์ค‘๊ณ ๋ฌผํ’ˆ ๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก

  • '์ƒํ’ˆ๋“ฑ๋ก' ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์ค‘๊ณ ๋ฌผํ’ˆ ๊ฒŒ์‹œ๊ธ€์„ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋Š” ํ™”๋ฉด์ด ๋‚˜์˜ต๋‹ˆ๋‹ค.
  • ์ œ๋ชฉ, ๊ฐ€๊ฒฉ, ์ƒํ’ˆ ์„ค๋ช…์„ ์“ฐ๊ณ  ์‚ฌ์ง„์„ ์ฒจ๋ถ€ํ•œ ํ›„์— ๊ฒŒ์‹œ๊ธ€์„ ์˜ฌ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

7-6. ์ค‘๊ณ ๋ฌผํ’ˆ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •

  • ์ค‘๊ณ ๋ฌผํ’ˆ ์ •๋ณด์™€ ํŒ๋งค ์ƒํƒœ ์ˆ˜์ •์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

7-7. ์ฑ„ํŒ…

7-8. ๋งˆ์ดํŽ˜์ด์ง€

  • '๋‚ด๊ฐ€ ์“ด ๊ธ€'์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž‘์„ฑํ•œ ๊ธ€์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ•ด๋‹น ํŽ˜์ด์ง€์—์„œ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  • ๋กœ๊ทธ์•„์›ƒ ํ•˜๋ฉด ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.