Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ kotlin {
implementation(libs.generativeai)
implementation(compose.uiTooling)
implementation(libs.ktor.client.okhttp)
implementation("androidx.paging:paging-runtime:3.3.0")
implementation("com.google.firebase:firebase-firestore-ktx:24.10.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.1")
implementation("com.google.firebase:firebase-bom:33.0.0")
}
commonMain.dependencies {
implementation(compose.runtime)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.developersbreach.kotlindictionarymultiplatform.di.appModules
import com.developersbreach.kotlindictionarymultiplatform.paging.androidPlatformModules
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin

Expand All @@ -18,6 +19,7 @@ class MainActivity : ComponentActivity() {
startKoin {
androidContext(this@MainActivity)
appModules()
androidPlatformModules()
}

setContent {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.developersbreach.kotlindictionarymultiplatform.paging

import org.koin.dsl.module

val androidPagingModule = module {
single<TopicPager> { createTopicPager() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.developersbreach.kotlindictionarymultiplatform.paging

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.Query
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.tasks.await

actual fun createTopicPager(): TopicPager {
return AndroidPagingTopicPager()
}

private class AndroidPagingTopicPager : TopicPager {

private val query = FirebaseFirestore.getInstance()
.collection("topics")
.orderBy("title", Query.Direction.ASCENDING)

override fun pages(): Flow<Page<Topic>> {
return channelFlow {
Pager(
config = PagingConfig(pageSize = 10, enablePlaceholders = false),
pagingSourceFactory = { FirestoreSource(query) },
).flow.collect { pagingData ->
val items = mutableListOf<Topic>()
pagingData.collect { topic ->
items.add(topic)
}
send(Page(items = items))
}
}
}
}

private class FirestoreSource(
private val query: Query,
) : PagingSource<DocumentSnapshot, Topic>() {

override suspend fun load(
params: LoadParams<DocumentSnapshot>,
): LoadResult<DocumentSnapshot, Topic> {
return try {
val currentQuery = params.key?.let { query.startAfter(it) } ?: query
val snapshot = currentQuery.limit(params.loadSize.toLong()).get().await()
val topics = snapshot.documents.map {
Topic(
id = it.id,
name = it.getString("title") ?: "",
description = it.getString("description") ?: "",
)
}
LoadResult.Page(
data = topics,
prevKey = null,
nextKey = snapshot.documents.lastOrNull(),
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}

override fun getRefreshKey(
state: PagingState<DocumentSnapshot, Topic>,
): DocumentSnapshot? {
return null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.developersbreach.kotlindictionarymultiplatform.paging

import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.developersbreach.kotlindictionarymultiplatform.data.topic.model.Topic
import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.Query
import kotlinx.coroutines.tasks.await

class FirestoreTopicPagingSource(
private val query: Query,
) : PagingSource<DocumentSnapshot, Topic>() {

override suspend fun load(
params: LoadParams<DocumentSnapshot>,
): LoadResult<DocumentSnapshot, Topic> {
return try {
val pageQuery = params.key?.let {
query.startAfter(it).limit(params.loadSize.toLong())
} ?: query.limit(params.loadSize.toLong())

val snapshot = pageQuery.get().await()

val topics = snapshot.documents.map { doc ->
Topic(
name = doc.getString("title"),
description = doc.getString("description"),
)
}

LoadResult.Page(
data = topics,
prevKey = null,
nextKey = snapshot.documents.lastOrNull(),
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}

override fun getRefreshKey(
state: PagingState<DocumentSnapshot, Topic>,
): DocumentSnapshot? {
return state.anchorPosition?.let { position ->
state.closestPageToPosition(position)?.nextKey
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.developersbreach.kotlindictionarymultiplatform.paging

import org.koin.dsl.module

val androidModule = module {
single<TopicPager> { createTopicPager() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ private fun TopicScreenPreview() {
searchQuery = "Search",
onQueryChange = { },
onTopicClick = { },
onLoadMore = {},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.developersbreach.kotlindictionarymultiplatform.paging

data class Page<T>(
val items: List<T>,
val nextCursor: Any? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.developersbreach.kotlindictionarymultiplatform.paging

data class Topic(
val id: String,
val name: String,
val description: String,
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic
package com.developersbreach.kotlindictionarymultiplatform.paging

import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
Expand All @@ -7,6 +7,8 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.ItemTopic
import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.TopicCard

@Composable
fun TopicList(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.developersbreach.kotlindictionarymultiplatform.paging

import kotlinx.coroutines.flow.Flow

interface TopicPager {
fun pages(): Flow<Page<Topic>>
}

expect fun createTopicPager(): TopicPager
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic
package com.developersbreach.kotlindictionarymultiplatform.paging

import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import com.developersbreach.kotlindictionarymultiplatform.ui.components.UiStateHandler
import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.TopicScreenUI
import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.TopicViewModel

@Composable
fun TopicScreen(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.DetailScreen
import com.developersbreach.kotlindictionarymultiplatform.ui.screens.detail.DetailViewModel
import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.TopicScreen
import com.developersbreach.kotlindictionarymultiplatform.paging.TopicScreen
import com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic.TopicViewModel
import org.koin.compose.viewmodel.koinViewModel

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.developersbreach.designsystem.components.KdScaffold
import com.developersbreach.kotlindictionarymultiplatform.paging.TopicList

@Composable
fun TopicScreenUI(
Expand All @@ -29,7 +30,9 @@ fun TopicScreenUI(
searchQuery = searchQuery,
onQueryChange = onQueryChange,
)

Spacer(modifier = Modifier.height(8.dp))

TopicList(
topics = topics,
onTopicClick = onTopicClick,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ data class TopicUi(
val topics: List<Topic> = emptyList(),
val searchQuery: String = "",
val filteredTopics: List<ItemTopic> = emptyList(),
val page: Int = 0,
val hasMore: Boolean = true,
)

data class ItemTopic(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,29 @@ package com.developersbreach.kotlindictionarymultiplatform.ui.screens.topic
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.developersbreach.kotlindictionarymultiplatform.data.topic.model.Topic
import com.developersbreach.kotlindictionarymultiplatform.data.topic.repository.TopicRepository
import com.developersbreach.kotlindictionarymultiplatform.paging.TopicPager
import com.developersbreach.kotlindictionarymultiplatform.ui.components.UiState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class TopicViewModel(
private val repository: TopicRepository,
private val pager: TopicPager,
) : ViewModel() {

private val _uiState: MutableStateFlow<UiState<TopicUi>> = MutableStateFlow(UiState.Loading)
val uiState: StateFlow<UiState<TopicUi>> = _uiState

private var rawTopics: List<Topic> = emptyList()
private var rawTopics: MutableList<Topic> = mutableListOf()

init {
viewModelScope.launch {
fetchTopicList()
}
}

private suspend fun fetchTopicList() {
_uiState.value = UiState.Success(TopicUi(isLoading = true))
repository.getTopics().fold(
ifLeft = { UiState.Error(it) },
ifRight = { list ->
rawTopics = list.sortedBy { it.name?.lowercase() ?: "" }
_uiState.value = UiState.Success(TopicUi(isLoading = true))
pager.pages().collect { page ->
rawTopics += page.items
applyFilters(rawTopics, (_uiState.value as UiState.Success).data.searchQuery)
},
)
}
}
}

fun updateSearchQuery(
Expand All @@ -57,7 +50,7 @@ class TopicViewModel(
)
}

_uiState.value = (_uiState.value as UiState.Success).copy(
_uiState.value = UiState.Success(
TopicUi(
isLoading = false,
searchQuery = query,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.developersbreach.kotlindictionarymultiplatform.paging

actual fun createTopicPager(): TopicPager {
TODO("Not yet implemented")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.developersbreach.kotlindictionarymultiplatform.paging

actual fun createTopicPager(): TopicPager {
TODO("Not yet implemented")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.developersbreach.kotlindictionarymultiplatform.paging

actual fun createTopicPager(): TopicPager {
TODO("Not yet implemented")
}
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ kotlinStdlib = "2.1.10"
runner = "1.6.2"
core = "1.6.1"
uiToolingPreviewAndroid = "1.8.2"
firebaseFirestoreKtx = "25.1.4"

[libraries]
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" }
Expand Down Expand Up @@ -70,6 +71,7 @@ kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", versio
androidx-runner = { group = "androidx.test", name = "runner", version.ref = "runner" }
androidx-core = { group = "androidx.test", name = "core", version.ref = "core" }
androidx-ui-tooling-preview-android = { group = "androidx.compose.ui", name = "ui-tooling-preview-android", version.ref = "uiToolingPreviewAndroid" }
firebase-firestore-ktx = { group = "com.google.firebase", name = "firebase-firestore-ktx", version.ref = "firebaseFirestoreKtx" }

[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
Expand Down
Loading