I'm having an issue with testing my library. For s...
# compose
z
I'm having an issue with testing my library. For some reason the tests never see the loadstate as loaded but the sample application works fine. I think it may be some misunderstanding with coroutines that I'm having.
PdfState
code is in 🧵
Copy code
@Test
fun `should load from file`() {
    lateinit var state: PdfState

    compose.setContent {
        state = rememberPdfState(this::class.java.getResource("/dummy.pdf")!!)
        Box(modifier = Modifier.size(width = 400.dp, height = 800.dp)) {
            PdfColumn(state)
        }
    }
    compose.mainClock.autoAdvance = true
    compose.waitUntil(10000) {
        state.loadState == LoadState.Loaded
    }
}
If anyone has any other tips for improvements I could make that would be appreciated
Copy code
/**
 * Manages the state and lifecycle of a PDF document in a Compose application
 */
@Stable
public class PdfState internal constructor() : AutoCloseable {
    private lateinit var renderer: PdfRenderer
    private val coroutineScope = CoroutineScope(Dispatchers.Default + SupervisorJob())

    private val _loadState = MutableStateFlow<LoadState>(LoadState.Loading)

    /**
     * The current loading state of the entire PDF document.
     */
    public val loadState: StateFlow<LoadState> = _loadState.asStateFlow()

    /**
     * The number of pages in the PDF. Returns 0 until a pdf is loaded.
     */
    public var pageCount: Int by mutableIntStateOf(0)
        private set

    /**
     * A flow that emits a [PdfPage] object for each page in the PDF document
     */
    public val pages: Flow<PdfPage> = channelFlow {
        val job = coroutineScope.launch {
            loadState.collectLatest { state ->
                if (state == LoadState.Loaded) {
                    for (index in 0 until pageCount) {
                        send(PdfPage(index, index)) // TODO: use id
                    }
                }
            }
        }
        awaitClose { job.cancel() }
    }

    internal constructor(initBlock: suspend () -> PdfRenderer) : this() {
        init(initBlock)
    }

    /**
     * Renders a specific page as an [ImageBitmap].
     *
     * @param index the zero-based page number
     * @param zoom the amount of zoom to apply as a multiplier (1.0f = 1x zoom)
     */
    public suspend fun loadPage(index: Int, zoom: Float = 1f): ImageBitmap {
        return withContext(coroutineScope.coroutineContext) {
            renderer.renderPage(index, zoom)
        }
    }

    /**
     * Gets the dimensions of a page without rendering it.
     *
     * @param index The zero-based page number
     */
    public fun getPageSize(index: Int): IntSize {
        return renderer.getPageSize(pageIndex = index, zoom = 1f)
    }

    /**
     * Releases all resources. Call when done with the PDF.
     * Automatically called when using provided PDF composables.
     */
    public override fun close() {
        renderer.close()
        coroutineScope.cancel()
    }

    internal fun init(initBlock: suspend () -> PdfRenderer) {
        coroutineScope.launch {
            try {
                _loadState.emit(LoadState.Loading)
                renderer = initBlock()
                pageCount = renderer.pageCount
                _loadState.emit(LoadState.Loaded)
            } catch (e: Exception) {
                _loadState.emit(LoadState.Error(e))
            }
        }
    }
}

/**
 * The state of loading a PDF file
 */
public sealed interface LoadState {
    public data object Loading : LoadState

    public data object Loaded : LoadState

    public data class Error(public val exception: Exception) : LoadState
}
r
Hi, why doesn't use view model and do something like this in your composable function: val canDownload by downloadViewModel.canDownloadState.collectAsStateWithLifecycle() // your state DisposableEffect(query) { // replace with path of file searchViewModel.search(query) // init method or similar with path of file onDispose { searchViewModel.resetSearch() // close method } } I haven't used the test library for compose, so I can't help you with that, but it could be that by not using viewmodel you're not within the lifecycle of the app. If you share the error log or debug more the state, can help you more.
z
Well this is a library, so I can't use a ViewModel, that would be left to the user
👍 1
So apparently the issue was from me comparing my
LoadState
class to a
StateFlow
But now im wondering why this even compiled at all? Shouldnt this have thrown a compilation error or warning? Is this a bug I found?