Kotlin flows map question about mapping lambda returning original type with a new generic type

128 Views Asked by At

I've been going through a Google sample app about Paging & mediator and I found an interesting flow map lambda case. You can find it below it is marked with a **.

I would assume that pagingData -> X.func would have the map return whatever the type is of X.func. But in this case, it's not.

pagingData is of type PagingData.

pagingData.map { UiModel.RepoItem(it) } should return a UiModel.RepoItem,

but according to the next map statement, the element has been converted to a PagingData<UiModel.RepoItem>. Shouldn't it be a UiModel.RepoItem?

repository.getSearchResultStream(queryString)
            **.map { pagingData -> pagingData.map {UiModel.RepoItem(it) }   } **
            .map {
                it.insertSeparators { before, after ->
                    if (after == null) {
                        // we're at the end of the list
                        return@insertSeparators null
                    }

                    if (before == null) {
                        // we're at the beginning of the list
                        return@insertSeparators UiModel.SeparatorItem("${after.roundedStarCount}0.000+ stars")
                    }
                    // check between 2 items
                    if (before.roundedStarCount > after.roundedStarCount) {
                        if (after.roundedStarCount >= 1) {
                            UiModel.SeparatorItem("${after.roundedStarCount}0.000+ stars")
                        } else {
                            UiModel.SeparatorItem("< 10.000+ stars")
                        }
                    } else {
                        // no separator
                        null
                    }
                }
            }

Here is the repository flow generating the flow. However, you do not need to read this it's not relevant.

    fun getSearchResultStream(query: String): Flow<PagingData<Repo>> {
        Log.d("GithubRepository", "New query: $query")

        // appending '%' so we can allow other characters to be before and after the query string
        val dbQuery = "%${query.replace(' ', '%')}%"
        val pagingSourceFactory = { database.reposDao().reposByName(dbQuery) }

        @OptIn(ExperimentalPagingApi::class)
        return Pager(
            config = PagingConfig(pageSize = NETWORK_PAGE_SIZE, enablePlaceholders = false),
            remoteMediator = GithubRemoteMediator(
                query,
                service,
                database
            ),
            pagingSourceFactory = pagingSourceFactory
        ).flow
    }
1

There are 1 best solutions below

3
On

Calling map on a Flow gives you another Flow with a different type. Calling map on a PagingData gives you another PagingData with a different type.

The inner map call converts the PagingData's type from one thing to another, but you still have a PagingData in the end.

Taking this line:

.map { pagingData -> pagingData.map {UiModel.RepoItem(it) }   }

And breaking it apart, we can see what's happening.

// This OUTER map call will return a Flow of the result of its lambda
.map { pagingData -> 
    // The only expression in the outer map call's lambda:
    pagingData.map { // This INNER map call will return a PagingData of the result of its lambda
        UiModel.RepoItem(it) // Each item of the source paging data is converted 
    }  // so this inner map call's result is a PagingData<UiModel.RepoItem>
} // and so the outer map call's result is a Flow<PagingData<UiModel.RepoItem>>