Skip to content

Commit 47ef3c9

Browse files
committed
Better PdfViewAdapter with retry
1 parent c242b4d commit 47ef3c9

File tree

1 file changed

+130
-33
lines changed

1 file changed

+130
-33
lines changed

pdfViewer/src/main/java/com/rajat/pdfviewer/PdfViewAdapter.kt

Lines changed: 130 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package com.rajat.pdfviewer
22

33
import android.content.Context
44
import android.graphics.Rect
5+
import android.os.Handler
6+
import android.os.Looper
7+
import android.util.Log
58
import android.view.LayoutInflater
69
import android.view.View
710
import android.view.ViewGroup
@@ -10,8 +13,10 @@ import android.view.animation.LinearInterpolator
1013
import androidx.recyclerview.widget.RecyclerView
1114
import com.rajat.pdfviewer.databinding.ListItemPdfPageBinding
1215
import com.rajat.pdfviewer.util.CommonUtils
13-
import kotlinx.coroutines.CoroutineScope
1416
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.MainScope
18+
import kotlinx.coroutines.Runnable
19+
import kotlinx.coroutines.cancel
1520
import kotlinx.coroutines.launch
1621
import kotlinx.coroutines.withContext
1722

@@ -24,66 +29,154 @@ internal class PdfViewAdapter(
2429
) : RecyclerView.Adapter<PdfViewAdapter.PdfPageViewHolder>() {
2530

2631
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PdfPageViewHolder =
27-
PdfPageViewHolder(ListItemPdfPageBinding.inflate(LayoutInflater.from(parent.context), parent, false))
32+
PdfPageViewHolder(
33+
ListItemPdfPageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
34+
)
2835

2936
override fun getItemCount(): Int = renderer.getPageCount()
3037

3138
override fun onBindViewHolder(holder: PdfPageViewHolder, position: Int) {
3239
holder.bind(position)
3340
}
3441

35-
inner class PdfPageViewHolder(private val itemBinding: ListItemPdfPageBinding) : RecyclerView.ViewHolder(itemBinding.root) {
42+
override fun onViewRecycled(holder: PdfPageViewHolder) {
43+
super.onViewRecycled(holder)
44+
holder.cancelJobs()
45+
}
46+
47+
inner class PdfPageViewHolder(private val itemBinding: ListItemPdfPageBinding) :
48+
RecyclerView.ViewHolder(itemBinding.root) {
49+
50+
private var currentBoundPage: Int = -1
51+
private var hasRealBitmap: Boolean = false
52+
private val fallbackHandler = Handler(Looper.getMainLooper())
53+
private var scope = MainScope()
54+
55+
private val DEBUG_LOGS_ENABLED = false
56+
3657
fun bind(position: Int) {
37-
val width = itemBinding.pageView.width.takeIf { it > 0 }
58+
cancelJobs()
59+
currentBoundPage = position
60+
hasRealBitmap = false
61+
scope = MainScope()
62+
63+
val displayWidth = itemBinding.pageView.width.takeIf { it > 0 }
3864
?: context.resources.displayMetrics.widthPixels
3965

40-
CoroutineScope(Dispatchers.Main).launch {
41-
itemBinding.pageLoadingLayout.pdfViewPageLoadingProgress.visibility =
42-
if (enableLoadingForPages) View.VISIBLE else View.GONE
66+
itemBinding.pageView.setImageBitmap(null)
67+
68+
itemBinding.pageLoadingLayout.pdfViewPageLoadingProgress.visibility =
69+
if (enableLoadingForPages) View.VISIBLE else View.GONE
4370

71+
scope.launch {
4472
val cached = withContext(Dispatchers.IO) {
4573
renderer.getBitmapFromCache(position)
4674
}
4775

48-
if (cached != null) {
76+
if (cached != null && currentBoundPage == position) {
77+
if (DEBUG_LOGS_ENABLED) Log.d("PdfViewAdapter", "✅ Loaded page $position from cache")
4978
itemBinding.pageView.setImageBitmap(cached)
50-
itemBinding.pageLoadingLayout.pdfViewPageLoadingProgress.visibility = View.GONE
79+
hasRealBitmap = true
5180
applyFadeInAnimation(itemBinding.pageView)
81+
itemBinding.pageLoadingLayout.pdfViewPageLoadingProgress.visibility = View.GONE
5282
return@launch
5383
}
5484

5585
renderer.getPageDimensionsAsync(position) { size ->
56-
val aspectRatio = size.width.toFloat() / size.height.toFloat()
57-
val height = (width / aspectRatio).toInt()
86+
if (currentBoundPage != position) return@getPageDimensionsAsync
5887

88+
val aspectRatio = size.width.toFloat() / size.height.toFloat()
89+
val height = (displayWidth / aspectRatio).toInt()
5990
itemBinding.updateLayoutParams(height)
6091

61-
val bitmap = CommonUtils.Companion.BitmapPool.getBitmap(width, maxOf(1, height))
62-
renderer.renderPage(position, bitmap) { success, pageNo, renderedBitmap ->
63-
if (success && pageNo == position) {
64-
CoroutineScope(Dispatchers.Main).launch {
65-
itemBinding.pageView.setImageBitmap(renderedBitmap ?: bitmap)
66-
applyFadeInAnimation(itemBinding.pageView)
67-
itemBinding.pageLoadingLayout.pdfViewPageLoadingProgress.visibility = View.GONE
68-
69-
// adaptive directional prefetch
70-
val direction = parentView.getScrollDirection()
71-
val fallbackHeight = itemBinding.pageView.height.takeIf { it > 0 }
72-
?: context.resources.displayMetrics.heightPixels
73-
74-
renderer.schedulePrefetch(
75-
currentPage = position,
76-
width = width,
77-
height = fallbackHeight,
78-
direction = direction
79-
)
80-
}
92+
renderAndApplyBitmap(position, displayWidth, height)
93+
}
94+
}
95+
96+
startPersistentFallbackRender(position)
97+
}
98+
99+
private fun renderAndApplyBitmap(page: Int, width: Int, height: Int) {
100+
val bitmap = CommonUtils.Companion.BitmapPool.getBitmap(width, maxOf(1, height))
101+
102+
renderer.renderPage(page, bitmap) { success, pageNo, rendered ->
103+
scope.launch {
104+
if (success && currentBoundPage == pageNo) {
105+
if (DEBUG_LOGS_ENABLED) Log.d("PdfViewAdapter", "✅ Render complete for page $pageNo")
106+
itemBinding.pageView.setImageBitmap(rendered ?: bitmap)
107+
hasRealBitmap = true
108+
applyFadeInAnimation(itemBinding.pageView)
109+
itemBinding.pageLoadingLayout.pdfViewPageLoadingProgress.visibility = View.GONE
110+
111+
val fallbackHeight = itemBinding.pageView.height.takeIf { it > 0 }
112+
?: context.resources.displayMetrics.heightPixels
113+
114+
renderer.schedulePrefetch(
115+
currentPage = pageNo,
116+
width = width,
117+
height = fallbackHeight,
118+
direction = parentView.getScrollDirection()
119+
)
120+
} else {
121+
if (DEBUG_LOGS_ENABLED) Log.w("PdfViewAdapter", "🚫 Skipping render for page $pageNo — ViewHolder now bound to $currentBoundPage")
122+
CommonUtils.Companion.BitmapPool.recycleBitmap(bitmap)
123+
retryRenderOnce(page, width, height)
124+
}
125+
}
126+
}
127+
}
128+
129+
private fun retryRenderOnce(page: Int, width: Int, height: Int) {
130+
val retryBitmap = CommonUtils.Companion.BitmapPool.getBitmap(width, height)
131+
renderer.renderPage(page, retryBitmap) { success, retryPageNo, rendered ->
132+
scope.launch {
133+
if (success && retryPageNo == currentBoundPage && !hasRealBitmap) {
134+
if (DEBUG_LOGS_ENABLED) Log.d("PdfViewAdapter", "🔁 Retry success for page $retryPageNo")
135+
itemBinding.pageView.setImageBitmap(rendered ?: retryBitmap)
136+
hasRealBitmap = true
137+
applyFadeInAnimation(itemBinding.pageView)
138+
itemBinding.pageLoadingLayout.pdfViewPageLoadingProgress.visibility = View.GONE
139+
} else {
140+
CommonUtils.Companion.BitmapPool.recycleBitmap(retryBitmap)
141+
}
142+
}
143+
}
144+
}
145+
146+
private fun startPersistentFallbackRender(
147+
page: Int,
148+
retries: Int = 10,
149+
delayMs: Long = 200L
150+
) {
151+
var attempt = 0
152+
153+
lateinit var task: Runnable
154+
task = object : Runnable {
155+
override fun run() {
156+
if (currentBoundPage != page || hasRealBitmap) return
157+
158+
scope.launch {
159+
val cached = withContext(Dispatchers.IO) {
160+
renderer.getBitmapFromCache(page)
161+
}
162+
163+
if (cached != null && currentBoundPage == page) {
164+
if (DEBUG_LOGS_ENABLED) Log.d("PdfViewAdapter", "🕒 Fallback applied for page $page on attempt $attempt")
165+
itemBinding.pageView.setImageBitmap(cached)
166+
hasRealBitmap = true
167+
applyFadeInAnimation(itemBinding.pageView)
168+
itemBinding.pageLoadingLayout.pdfViewPageLoadingProgress.visibility = View.GONE
81169
} else {
82-
CommonUtils.Companion.BitmapPool.recycleBitmap(bitmap)
170+
attempt++
171+
if (attempt < retries) {
172+
fallbackHandler.postDelayed(task, delayMs)
173+
}
83174
}
84175
}
85176
}
86177
}
178+
179+
fallbackHandler.postDelayed(task, delayMs)
87180
}
88181

89182
private fun ListItemPdfPageBinding.updateLayoutParams(height: Int) {
@@ -95,11 +188,15 @@ internal class PdfViewAdapter(
95188
}
96189
}
97190

191+
fun cancelJobs() {
192+
scope.cancel()
193+
}
194+
98195
private fun applyFadeInAnimation(view: View) {
99196
view.startAnimation(AlphaAnimation(0F, 1F).apply {
100197
interpolator = LinearInterpolator()
101198
duration = 300
102199
})
103200
}
104201
}
105-
}
202+
}

0 commit comments

Comments
 (0)