Skip to content

Commit

Permalink
Optimize WorkflowScreen by Paging
Browse files Browse the repository at this point in the history
  • Loading branch information
SanmerDev committed Aug 2, 2024
1 parent 8ac848d commit 706ad54
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 55 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ dependencies {
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.lifecycle.viewmodel.savedstate)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.paging.compose)
implementation(libs.androidx.paging.runtime)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization.json)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package dev.sanmer.github.artifacts.ui.ktx

import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.runtime.Composable
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey

inline fun <T : Any> LazyListScope.items(
items: LazyPagingItems<T>,
noinline key: ((T) -> Any)? = null,
noinline contentType: (T) -> Any? = { null },
crossinline itemContent: @Composable LazyItemScope.(item: T) -> Unit
) = items(
count = items.itemCount,
key = items.itemKey(key),
contentType = items.itemContentType(contentType)
) {
itemContent(items[it]!!)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,14 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import dev.sanmer.github.artifacts.R
import dev.sanmer.github.artifacts.model.LoadData
import dev.sanmer.github.artifacts.ui.component.Failed
import dev.sanmer.github.artifacts.ui.component.Loading
import dev.sanmer.github.artifacts.ui.component.NavigateUpTopBar
Expand All @@ -30,14 +28,14 @@ fun WorkflowScreen(
viewModel: WorkflowViewModel = hiltViewModel(),
navController: NavController
) {
val workflowRuns by viewModel.workflowRuns.collectAsStateWithLifecycle()
val workflowRuns = viewModel.workflowRuns.collectAsLazyPagingItems()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()

Scaffold(
topBar = {
TopBar(
name = viewModel.name,
onRefresh = viewModel::updateWorkflows,
onRefresh = workflowRuns::refresh,
navController = navController,
scrollBehavior = scrollBehavior
)
Expand All @@ -47,32 +45,25 @@ fun WorkflowScreen(
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection)
.fillMaxSize(),
targetState = workflowRuns,
targetState = workflowRuns.loadState.refresh,
label = "WorkflowScreen"
) { data ->
when (data) {
LoadData.Loading, LoadData.None -> Loading(
) { state ->
when (state) {
is LoadState.Error -> Failed(
message = state.error.message,
modifier = Modifier.padding(contentPadding)
)

is LoadData.Failure -> Failed(
message = data.error.message,
is LoadState.Loading -> Loading(
modifier = Modifier.padding(contentPadding)
)

is LoadData.Success -> if (data.value.isEmpty()) {
Failed(
message = stringResource(id = R.string.no_workflow),
modifier = Modifier.padding(contentPadding)
)
} else {
WorkflowList(
workflowRuns = data.value,
getArtifacts = viewModel::getArtifacts,
downloadArtifact = viewModel::downloadArtifact,
contentPadding = contentPadding
)
}
else -> WorkflowList(
workflowRuns = workflowRuns,
getArtifacts = viewModel::getArtifacts,
downloadArtifact = viewModel::downloadArtifact,
contentPadding = contentPadding
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
Expand All @@ -34,15 +33,17 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.paging.compose.LazyPagingItems
import dev.sanmer.github.artifacts.R
import dev.sanmer.github.artifacts.job.ArtifactJob
import dev.sanmer.github.artifacts.model.LoadData
import dev.sanmer.github.artifacts.ui.ktx.items
import dev.sanmer.github.response.Artifact
import dev.sanmer.github.response.WorkflowRun

@Composable
fun WorkflowList(
workflowRuns: List<WorkflowRun>,
workflowRuns: LazyPagingItems<WorkflowRun>,
getArtifacts: (WorkflowRun) -> LoadData<List<Artifact>>,
downloadArtifact: (Context, Artifact) -> Unit,
state: LazyListState = rememberLazyListState(),
Expand All @@ -56,7 +57,10 @@ fun WorkflowList(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(5.dp)
) {
items(workflowRuns) { run ->
items(
items = workflowRuns,
key = { it.id }
) { run ->
WorkflowItem(
run = run,
getArtifacts = getArtifacts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.cachedIn
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.sanmer.github.GitHubHandler
import dev.sanmer.github.GitHubHandler.Event
Expand All @@ -18,9 +24,8 @@ import dev.sanmer.github.artifacts.model.LoadData.None.asLoadData
import dev.sanmer.github.artifacts.repository.DbRepository
import dev.sanmer.github.response.Artifact
import dev.sanmer.github.response.WorkflowRun
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
Expand All @@ -38,8 +43,8 @@ class WorkflowViewModel @Inject constructor(
private var token = ""
private val handler by lazy { GitHubHandler(token) }

private val workflowRunsFlow = MutableStateFlow<LoadData<List<WorkflowRun>>>(LoadData.Loading)
val workflowRuns get() = workflowRunsFlow.asStateFlow()
var workflowRuns: Flow<PagingData<WorkflowRun>> = emptyFlow()
private set

private val artifacts = mutableStateMapOf<Long, LoadData<List<Artifact>>>()

Expand All @@ -56,33 +61,17 @@ class WorkflowViewModel @Inject constructor(
owner = repo.owner
name = repo.name

listWorkflows()
}
}
}

private fun listWorkflows() = with(handler) {
viewModelScope.launch {
workflowRunsFlow.update {
runCatching {
listWorkflowRuns(
workflowRuns = WorkflowRunPagingSource(
handler = handler,
owner = owner,
name = name,
event = Event.Push,
status = Status.Success,
perPage = 30,
page = 1
)
}.asLoadData()
}
perPage = 20
).asPager().flow
.cachedIn(this)
}
}
}

fun updateWorkflows() {
workflowRunsFlow.update { LoadData.Loading }
listWorkflows()
}

fun getArtifacts(run: WorkflowRun) = with(handler) {
viewModelScope.launch {
artifacts.getOrPut(run.id) {
Expand All @@ -107,6 +96,49 @@ class WorkflowViewModel @Inject constructor(
)
}

data class WorkflowRunPagingSource(
private val handler: GitHubHandler,
private val owner: String,
private val name: String,
private val event: Event = Event.Push,
private val status: Status = Status.Success,
private val perPage: Int = 30
) : PagingSource<Int, WorkflowRun>() {
override fun getRefreshKey(state: PagingState<Int, WorkflowRun>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, WorkflowRun> {
return try {
val page = params.key ?: 1
val workflowRuns = handler.listWorkflowRuns(
owner = owner,
name = name,
event = event,
status = status,
perPage = perPage,
page = page
)

LoadResult.Page(
data = workflowRuns,
prevKey = if (page == 1) null else page.minus(1),
nextKey = if (workflowRuns.size != perPage) null else page.plus(1),
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}

fun asPager() = Pager(
config = PagingConfig(pageSize = perPage),
pagingSourceFactory = { copy() }
)
}

companion object Util {
private val SavedStateHandle.id: Long
inline get() = checkNotNull(get("id"))
Expand Down
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ androidxCoreSplashscreen = "1.0.1"
androidxHiltNavigationCompose = "1.2.0"
androidxLifecycle = "2.8.4"
androidxNavigation = "2.7.7"
androidxPaging = "3.3.1"
androidxRoom = "2.6.1"
hilt = "2.51.1"
kotlin = "2.0.0"
Expand Down Expand Up @@ -43,6 +44,8 @@ androidx-lifecycle-service = { module = "androidx.lifecycle:lifecycle-service",
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidxLifecycle" }
androidx-lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "androidxLifecycle" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidxNavigation" }
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "androidxPaging" }
androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "androidxPaging" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidxRoom" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidxRoom" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidxRoom" }
Expand Down

0 comments on commit 706ad54

Please sign in to comment.