๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿค2024 ์•ˆ๋“œ๋กœ์ด๋“œ/๐Ÿฟ ์˜ํ™” ํ”„๋กœ์ ํŠธ ๊ฐœ๋ฐœ ์ผ์ง€

โ˜บ๏ธAndroid : flow๋ฅผ ์ˆœ์ฐจ์ ์œผ๋กœ ์‚ฌ์šฉํ•ด๋ณด์ž~!

by hyeonha 2024. 11. 4.

๋ชฉ์ฐจ

    ์š”๊ตฌ์‚ฌํ•ญ ์ •๋ฆฌํ•˜๊ธฐ 

    ๊ตฌํ˜„ํ•ด์•ผํ•  ์š”๊ตฌ์‚ฌํ•ญ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

    ใ…Œ

    ๋จผ์ € ์ƒํ™ฉ์„ ์ดํ•ดํ•ด๋ณด๋ฉด

    ๊ธฐ์กด์—๋Š” ํŒ”๋กœ์šฐ ์—ฌ๋ถ€์— ๋”ฐ๋ฅธ ์ฒ˜๋ฆฌ์—†์ด ํŒ”๋กœ์›Œ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฆฌ์‚ฌ์ดํด๋Ÿฌ๋ทฐ๋กœ ๋ณด์—ฌ์ฃผ์—ˆ๋‹ค.

    ๋”ฐ๋ผ์„œ Flow<ํŒ”๋กœ์›Œ ๋ฆฌ์ŠคํŠธ>๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”  ์œ ์ฆˆ์ผ€์ด์Šค๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์€ ํ›„ ์ด๋ฅผ StateFlow ๋ณ€์ˆ˜์— ๋„ฃ์–ด์ฃผ๊ณ  UI๋ฅผ ์—…๋ฐ์ดํŠธํ•ด์ฃผ์—ˆ๋‹ค. 

     

    ์ถ”๊ฐ€๋œ ์‚ฌํ•ญ์€ ๋‚ด๊ฐ€ ํ•ด๋‹น ์œ ์ €์— ๋Œ€ํ•œ ํŒ”๋กœ์šฐ ์—ฌ๋ถ€์— ๋”ฐ๋ผ UI์š”์†Œ์— ๋ณ€ํ™”๋ฅผ ์ฃผ๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.

    ๋”ฐ๋ผ์„œ ํŒ”๋กœ์šฐ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์œ ์ฆˆ ์ผ€์ด์Šค์™€ ์œ ์ €์— ๋Œ€ํ•œ ํŒ”๋กœ์šฐ ์—ฌ๋ถ€๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์œ ์ฆˆ ์ผ€์ด์Šค๋ฅผ ๊ฒฐํ•ฉํ•œ ํ›„ ๊ฒฐํ•ฉํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฆฌ์‚ฌ์ดํด๋Ÿฌ๋ทฐ์— ์ „๋‹ฌํ•ด์ฃผ์–ด์•ผํ–ˆ๋‹ค.

     

    ๊ทธ๋ ‡๋‹ค๋ฉด ๋‘ ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ฒฐํ•ฉํ•˜๋ฉด ์ข‹์„๊นŒ? 

     

    ์ฐพ์•„๋ณธ ๊ฒฐ๊ณผ combine๊ณผ zip, flatMapLatest์— ๋Œ€ํ•ด์„œ ๋‚˜์™”๋‹ค. 

     flow๋ฅผ collectํ•˜๋Š” ๊ฒƒ๊ณผ ์–ด๋–ป๊ฒŒ ๋‹ค๋ฅด๊ณ , ๊ฐ ์ฐจ์ด๊ฐ€ ๋ญ”์ง€, ๋‚ด ๊ธฐ๋Šฅ ๊ตฌํ˜„์—๋Š” ์–ด๋–ค ๋ฐฉ์‹์ด ์ ํ•ฉํ•œ์ง€ ์•Œ์•„๋ณธ ๊ณผ์ •์„ ์ •๋ฆฌํ•ด์„œ ๊ธฐ๋กํ•ด๋‘๋ ค๊ณ  ํ•œ๋‹ค.


     flow๋ฅผ collect ํ•˜๋Š” ๊ฒƒ์˜ ์˜๋ฏธ

    flow๋Š” ์—ฌ๋Ÿฌ ๊ฐ’์„ ์ˆœ์ฐจ์ ์œผ๋กœ ๋‚ด๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆผ์ด๋‹ค. 

    flow๋ฅผ collectํ•˜๊ฒŒ ๋˜๋ฉด ๋ฐ์ดํ„ฐ ๋ฐฉ์ถœ์ด ์‹œ์ž‘๋˜๊ณ , ๋ฐฉ์ถœ๋œ ์ˆœ์„œ๋Œ€๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. 

    collect ์—ฐ์‚ฐ์ž๋Š” ๊ฐ ๊ฐ’์„ ์ˆ˜์‹ ํ•  ๋•Œ๊นŒ์ง€ ์ผ์‹œ ์ค‘์ง€ํ•˜๊ณ  ๊ฐ ๊ฐ’์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค. 

    flow์˜ ์ƒ์„ฑ๋ถ€ํ„ฐ ์ฒ˜๋ฆฌ๊นŒ์ง€์˜ ํ๋ฆ„์„ ๊ทธ๋ ค๋ณด์•˜๋‹ค

    1๏ธโƒฃ map ์‚ฌ์šฉ 

    Map์„ ํ†ตํ•ด ๊ธฐ์กด flow๋ฅผ ๋ณ€ํ™˜์‹œ์ผœ ๋‚ด๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค. 

    ํŒ”๋กœ์›Œ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ ธ์™€์„œ followerUserInfoList.map์„ ํ†ตํ•ด ๊ฐ ํŒ”๋กœ์›Œ ์œ ์ €์— ๋Œ€ํ•ด ๋‚ด๊ฐ€ ํŒ”๋กœ์ž‰ ์ค‘์ธ์ง€ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๋Š” ์œ ์ฆˆ ์ผ€์ด์Šค ํ˜ธ์ถœ ํ›„ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒฐํ•ฉํ•ด์„œ _mutualFollowerList.value์— ๋ฐ˜์˜ํ•ด์ฃผ์—ˆ๋‹ค. 

     fun getFollowerList(userId: String) {
                viewModelScope.launch {
                    getFollowerListUseCase(userId)
                        .map {
                            it.followerUserInfoList
                                .map { userInfo ->
                                    userInfo.userId?.let { it1 ->
                                        val isFollowing = checkIsFollowUseCase(it1).first()?.isFollowing ?: false
                                        MutualFollowUserInfo(
                                            email = userInfo.email,
                                            userId = userInfo.userId,
                                            type = userInfo.type,
                                            nickname = userInfo.nickname,
                                            profileUrl = userInfo.profileUrl,
                                            lastLoginDate = userInfo.lastLoginDate,
                                            introduction = userInfo.introduction,
                                            roles = userInfo.roles,
                                            createDate = userInfo.createDate,
                                            lastModifiedDate = userInfo.lastModifiedDate,
                                            isFollowing = isFollowing,
                                        )
                                    } ?: MutualFollowUserInfo()
                                }
                        }.collect {
                            _mutualFollowerList.value = it
                        }
                }
            }

    map์œผ๋กœ ๊ตฌํ˜„ํ•œ ๊ฒฐ๊ณผ์ด๋‹ค.

     

    ์ด ์ฝ”๋“œ๋กœ ๊ธฐ๋Šฅ์€ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๋ชจ๋‘ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค. ๋ชจ๋‘ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋Š” ๊ฒฝ์šฐ ํŒ”๋กœ์›Œ ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋งŽ์•„์ง€๋ฉด ๋Š๋ ค์งˆ ์ˆ˜ ์žˆ๊ฒ ๋‹ค 

    ์ˆœ์ฐจ ๊ณผ์ • ํ™•์ธ ๋กœ๊ทธ : ๊ฐ ์•„์ดํ…œ์— ๋Œ€ํ•ด ์‹œ์ž‘ ~ ์™„๋ฃŒ๊ฐ€ ์ˆœ์ฐจ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค.


    ๊ฒฐ๊ณผ

    ํ˜„์žฌ ๊ตฌํ˜„ํ•  ๋กœ์ง์˜ ๊ฒฝ์šฐ ๊ฐ ํŒ”๋กœ์›Œ์— ๋Œ€ํ•œ userId๋ฅผ ์ด์šฉํ•ด์„œ ๋‚˜์˜ ํ•ด๋‹น ์œ ์ €์— ๋Œ€ํ•œ ํŒ”๋กœ์šฐ ์—ฌ๋ถ€๋ฅผ ์–ป์–ด์•ผํ•œ๋‹ค.

    ์ฆ‰  ๊ฐ ํŒ”๋กœ์›Œ์— ๋Œ€ํ•œ userId -> ํŒ”๋กœ์šฐ ์—ฌ๋ถ€ ๋ผ๋Š” ์ˆœ์„œ๊ฐ€ ์กด์žฌํ•œ๋‹ค.

     

    ๋”ฐ๋ผ์„œ map์„ ํ†ตํ•ด ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ ์ ˆํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. 

    ๊ทธ๋Ÿฌ๋‚˜ ๋งŒ์•ฝ ํŒ”๋กœ์›Œ ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋Š˜์–ด๋‚˜์„œ ์ˆœ์ฐจ์  ์ฒ˜๋ฆฌ์— ๋Š๋ฆฐ ์†๋„๊ฐ€ ๋Š๊ปด์ง„๋‹ค๋ฉด ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋กœ ๊ฐœ์„ ํ•ด๋ณด๊ธฐ๋กœ ํ•œ๋‹ค.


    ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์— ๋Œ€ํ•ด ๊ถ๊ธˆํ•ด์„œ ํ…Œ์ŠคํŠธํ•ด๋ณด์•˜๋‹ค!

    โž•map & combine์„ ํ†ตํ•ด ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ

    ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ–ˆ์„ ๋•Œ์˜ ์ถœ๋ ฅ

    ์•„๊นŒ map์œผ๋กœ ์ˆœ์ฐจ์ฒ˜๋ฆฌํ–ˆ์„ ๋•Œ์™€ ๋‹ฌ๋ฆฌ combine์„ ํ†ตํ•ด ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•ด์ค€ ๊ฒฝ์šฐ ํŒ”๋กœ์›Œ์— ๋Œ€ํ•ด์„œ ์ƒํƒœ ํ™•์ธ ์‹œ์ž‘ ~ ์™„๋ฃŒ๊ฐ€ ๋๋‚œ ํ›„ ๋‹ค์Œ ํŒ”๋กœ์›Œ์— ๋Œ€ํ•œ ๋กœ์ง์ด ์ˆ˜ํ–‰๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๊ฐ  ํŒ”๋กœ์›Œ์— ๋Œ€ํ•œ ์ƒํƒœ ํ™•์ธ ์‹œ์ž‘ ~ ์™„๋ฃŒ๊นŒ์ง€๊ฐ€ ๋ณ‘๋ ฌ๋กœ ์ด๋ฃจ์–ด์ง€๊ณ   ์žˆ๋‹ค.

    ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค!

         // 2 . ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ(map & combine ์‚ฌ์šฉ)
            fun getFollowingListParallel(userId: String) {
                viewModelScope.launch {
                    getFollowingListUseCase(userId)
                        .map { followingResponse ->
                            println("1. ํŒ”๋กœ์ž‰ ์‘๋‹ต ์ฒ˜๋ฆฌ ์‹œ์ž‘")
                            // null ์ฒดํฌ ํ›„ ๋ฆฌ์ŠคํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ
                            val userList = followingResponse?.followingUserInfoList ?: emptyList()
    
                            // ๊ฐ ์‚ฌ์šฉ์ž๋ณ„๋กœ Flow ์ƒ์„ฑ
                            val followStatusFlows =
                                userList.map { user ->
                                    println("2. ์‚ฌ์šฉ์ž ${user.userId} ํŒ”๋กœ์šฐ ์ƒํƒœ ํ™•์ธ ์‹œ์ž‘")
                                    // ๊ฐ ์‚ฌ์šฉ์ž์˜ ํŒ”๋กœ์šฐ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๋Š” Flow ์ƒ์„ฑ
                                    checkIsFollowUseCase(user.userId ?: "").map { followResponse ->
                                        println("3. ์‚ฌ์šฉ์ž ${user.userId} ํŒ”๋กœ์šฐ ์ƒํƒœ ํ™•์ธ ์™„๋ฃŒ")
                                        MutualFollowUserInfo(
                                            email = user.email,
                                            userId = user.userId,
                                            type = user.type,
                                            nickname = user.nickname,
                                            profileUrl = user.profileUrl,
                                            lastLoginDate = user.lastLoginDate,
                                            introduction = user.introduction,
                                            roles = user.roles,
                                            createDate = user.createDate,
                                            lastModifiedDate = user.lastModifiedDate,
                                            isFollowing = followResponse?.isFollowing,
                                        )
                                    }
                                }
    
                            // Flow ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋น„์–ด์žˆ๋Š” ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ
                            if (followStatusFlows.isEmpty()) {
                                flow { emit(emptyList()) }
                            } else {
                                // combine์„ ์‚ฌ์šฉํ•ด ๋ชจ๋“  Flow๋ฅผ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌ
                                println("4. ํŒ”๋กœ์ž‰ ๋ฆฌ์ŠคํŠธ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ์‹œ์ž‘")
                                combine(followStatusFlows) { userInfoArray ->
                                    println("5. ํŒ”๋กœ์ž‰ ๋ฆฌ์ŠคํŠธ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ์™„๋ฃŒ")
                                    userInfoArray.toList()
                                }
                            }
                        }.collect {
                        
                        // ์—ฌ๊ธฐ์„œ๋Š” ๊ฒฐ๊ณผ๊ฐ€ Flow ํ˜•ํƒœ๋กœ ์™€์„œ ์–ด๋–ป๊ฒŒ ๋ณ€ํ™”๋ฅผ ํ•ด์ค„ ์ง€ ๋ชจ๋ฅด๊ฒ ์–ด์„œ ์ผ๋‹จ first()๋ฅผ ์‚ฌ์šฉํ•ด์ฃผ์—ˆ๋‹ค. 
                            _followingList.value = it.first()
                        }
                }
            }

     

    map๊ณผ combine์— ๋Œ€ํ•œ ์„ค๋ช…

    - map : ์›๋ž˜์˜ Flow์—์„œ ๊ฐ ๊ฐ’์— ๋ณ€ํ™˜ ํ•จ์ˆ˜๋ฅผ ์ ์šฉํ•œ ๊ฒฐ๊ณผ๋ฅผ ํฌํ•จํ•œ flow๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค.

    - combine : ๊ฐ flow์—์„œ ๋ฐฉ์ถœ๋œ ๊ฐ€์žฅ ์ตœ๊ทผ์˜ ๊ฐ’์„ ๊ฒฐํ•ฉํ•˜์—ฌ ๋ณ€ํ™˜ ํ•จ์ˆ˜๋ฅผ ์ ์šฉํ•˜์—ฌ ์ƒ์„ฑ๋œ ๊ฐ’๋“ค์˜ flow๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

    combine์„ ํ†ตํ•ด์„œ๋Š” ๊ฐ Flow๊ฐ€ ๋™์‹œ์— ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐ’์„ ๋ฐฉ์ถœํ•  ์ˆ˜ ์žˆ์–ด ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค. 

     

    ๊ณต์‹ ์˜ˆ์ œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค. 

    val flow = flowOf(1, 2).onEach { delay(10) }
    val flow2 = flowOf("a", "b", "c").onEach { delay(15) }
    combine(flow, flow2) { i, s -> i.toString() + s }.collect {
        println(it) // Will print "1a 2a 2b 2c"
    }

     

    ๊ทธ๋Ÿฐ๋ฐ ์ด๋Ÿด ๊ฒฝ์šฐ ๋‚˜๋Š” ํŒ”๋กœ์›Œ ๋ฆฌ์ŠคํŠธ ์–ป๊ธฐ ->  ๊ฐ ํŒ”๋กœ์›Œ ๋ฆฌ์ŠคํŠธ์˜ userId ์–ป๊ธฐ -> userId๋กœ ํŒ”๋กœ์šฐ ์—ฌ๋ถ€ ํ™•์ธํ•˜๊ธฐ

    ๋ฅผ ์ฒ˜๋ฆฌํ•ด์ค˜์•ผํ•œ๋‹ค.

    ์ด ๋•Œ combine์œผ๋กœ ํ•  ๊ฒฝ์šฐ ํŒ”๋กœ์ž‰ ์—ฌ๋ถ€๋ฅผ ์–ป๊ธฐ ์ „์— ๊ฐ€์žฅ ์ตœ๊ทผ์˜ ๊ฐ’์ด B ํŒ”๋กœ์›Œ ๋ฆฌ์ŠคํŠธ์˜ ๊ฐ’ true์˜€์„ ๊ฒฝ์šฐ์— A ํŒ”๋กœ์›Œ ๋ฆฌ์ŠคํŠธ์—  B ํŒ”๋กœ์›Œ ์—ฌ๋ถ€๊ฐ€ ์ ์šฉ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜๋„ ์žˆ์„๊นŒ??

    ๊ฒฐ๋ก ๋ถ€ํ„ฐ ๋งํ•˜๋ฉด ์•„๋‹ˆ์—ˆ๋‹ค

     

    ๋‚˜์˜ ๊ฒฝ์šฐ ๊ฐ ํŒ”๋กœ์›Œ์— ๋Œ€ํ•˜์—ฌ userId๋ฅผ ์ด์šฉํ•ด์„œ ํŒ”๋กœ์šฐ ์—ฌ๋ถ€๋ฅผ ์–ป๊ณ  ์žˆ๋‹ค. ๊ตฌ์ฒด์ ์œผ๋กœ ํ’€์–ด๋ณด๋ฉด 

    - checkIsFollowUseCase๋Š” ๊ฐ ํŒ”๋กœ์›Œ์˜ userId์— ๋”ฐ๋ผ ๊ณ ์œ ์˜ flow๋กœ ์ž‘๋™ํ•˜๊ณ  ์žˆ๋‹ค. 

    - combine์€ ๊ฐ flow์—์„œ ๋ฐฉ์ถœ๋œ ์ตœ์‹ ๊ฐ’๋“ค์„ ๋ชจ์•„ ๋ฐฐ์—ด๋กœ ์ƒ์„ฑํ•˜๋ฏ€๋กœ , ๊ฐ ํŒ”๋กœ์›Œ์— ๋Œ€ํ•ด ์กฐํšŒ๋œ ํŒ”๋กœ์›Œ ์ƒํƒœ๊ฐ€ ๋‹ค๋ฅธ ํŒ”๋กœ์›Œ์˜ ์ •๋ณด์™€ ์„ž์ด์ง€ ์•Š๋Š”๋‹ค.

     

    ์ฆ‰ combine์„  ํ†ตํ•ด ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ ํŒ”๋กœ์›Œ์— ๋Œ€ํ•œ ๋‚˜์˜ ํŒ”๋กœ์šฐ ์ •๋ณด๋ฅผ ๋งคํ•‘ํ•˜๊ณ  ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

     

    728x90