You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

纯原生Android实现Paytm/Flipkart式滚动加载与离线缓存方案求助

Implementing Scroll-to-Load & Offline Caching for Native Android (Like Paytm/Flipkart)

Hey there! As a fellow native Android dev, I’ve built similar infinite scroll and offline caching features for apps with complex layouts and multiple APIs—let’s walk through how to implement this step by step, tailored to your project’s needs.

1. Scroll-to-Load (Infinite Scrolling) with RecyclerView

This is the core of the "keep scrolling to see more" experience. We’ll use RecyclerView’s scroll listener to trigger data loads when the user nears the bottom of the list.

Step-by-Step Implementation

  • Add a loading footer to your adapter: Include a dedicated loading item type to show a spinner while fetching new data.
  • Implement a reusable scroll listener: Avoid duplicating code across your 30+ screens by wrapping the scroll logic in a reusable class.
  • Guard against duplicate requests: Use an isLoading flag to prevent triggering multiple API calls at once.

Example scroll listener code:

class LoadMoreScrollListener(
    private val layoutManager: LinearLayoutManager,
    private val onLoadMore: () -> Unit
) : RecyclerView.OnScrollListener() {
    private var isLoading = false
    private val visibleThreshold = 3 // Trigger load when 3 items are left before end

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)
        val totalItemCount = layoutManager.itemCount
        val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()

        if (!isLoading && lastVisibleItemPosition >= totalItemCount - visibleThreshold) {
            isLoading = true
            onLoadMore.invoke()
        }
    }

    // Call this when your API request completes (success or failure)
    fun resetLoadingState() {
        isLoading = false
    }
}

Attach it to your RecyclerView like this:

val scrollListener = LoadMoreScrollListener(layoutManager) {
    // Fetch next page of data from your API
    fetchNextPage()
}
recyclerView.addOnScrollListener(scrollListener)

2. Offline Caching Strategy

You need a two-tier caching system (memory + disk) to ensure data is available even when the network drops. Here’s how to set it up:

Memory Cache (Fast Access)

Use LruCache to store frequently accessed data in memory—great for quick UI updates without hitting disk.

val memoryCache = LruCache<String, Any>(10 * 1024 * 1024) // 10MB limit

// Store data
memoryCache.put("api_product_list_1", productList)

// Retrieve data
val cachedProducts = memoryCache.get("api_product_list_1") as? List<Product>

Disk Cache (Persistent Storage)

Use Room Database for persistent disk caching—it’s built for Android, handles migrations, and integrates seamlessly with coroutines. Create entity classes for each API’s response, and define DAOs to read/write cached data.

Example entity for a product list:

@Entity(tableName = "cached_products")
data class CachedProduct(
    @PrimaryKey val id: Int,
    val name: String,
    val price: Double,
    val timestamp: Long // For cache invalidation
)

Network-Level Caching with OkHttp

Add an OkHttp interceptor to automatically cache API responses based on network state:

val cacheInterceptor = Interceptor { chain ->
    val request = chain.request()
    val response = chain.proceed(request)

    val cacheControl = if (isNetworkAvailable(context)) {
        // Cache for 5 minutes when online
        CacheControl.Builder().maxAge(5, TimeUnit.MINUTES).build()
    } else {
        // Cache for 7 days when offline
        CacheControl.Builder().maxStale(7, TimeUnit.DAYS).build()
    }

    response.newBuilder()
        .header("Cache-Control", cacheControl.toString())
        .build()
}

// Initialize OkHttp with cache
val cache = Cache(File(context.cacheDir, "http_cache"), 20 * 1024 * 1024) // 20MB cache
val okHttpClient = OkHttpClient.Builder()
    .cache(cache)
    .addInterceptor(cacheInterceptor)
    .build()

Cache Invalidation

Add timestamp checks to your Room entities—when fetching new data, compare the cached timestamp with a server-provided updated time, or invalidate old cache after a set period.

3. Handling Multiple Layouts

For screens with mixed layouts (e.g., banners, products, ads), use RecyclerView’s getItemViewType to manage different view holders:

class MultiLayoutAdapter(private val items: List<BaseListItem>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    companion object {
        const val TYPE_BANNER = 0
        const val TYPE_PRODUCT = 1
        const val TYPE_LOADING = 2
    }

    override fun getItemViewType(position: Int): Int {
        return when (items[position]) {
            is BannerItem -> TYPE_BANNER
            is ProductItem -> TYPE_PRODUCT
            is LoadingItem -> TYPE_LOADING
            else -> throw IllegalArgumentException("Unknown item type")
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            TYPE_BANNER -> BannerViewHolder(inflater.inflate(R.layout.item_banner, parent, false))
            TYPE_PRODUCT -> ProductViewHolder(inflater.inflate(R.layout.item_product, parent, false))
            TYPE_LOADING -> LoadingViewHolder(inflater.inflate(R.layout.item_loading, parent, false))
            else -> throw IllegalArgumentException("Unknown view type")
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is BannerViewHolder -> holder.bind(items[position] as BannerItem)
            is ProductViewHolder -> holder.bind(items[position] as ProductItem)
            is LoadingViewHolder -> {} // No binding needed for loading state
        }
    }

    override fun getItemCount() = items.size
}

// Base class for all list items
abstract class BaseListItem
data class BannerItem(val imageUrl: String) : BaseListItem()
data class ProductItem(val id: Int, val name: String) : BaseListItem()
object LoadingItem : BaseListItem()

4. Managing 30+ APIs Efficiently

  • Build a generic network layer: Use Retrofit with a base API service interface to avoid repeating request setup. Add wrapper functions to handle cache checks before making network calls.
  • Reuse load-more logic: Create a base LoadMoreViewModel or LoadMoreRepository that handles pagination, loading states, and cache syncing—extend this for each screen’s specific API.
  • Per-API cache rules: For APIs that don’t need frequent updates (e.g., static content), set longer cache durations. For real-time data (e.g., order status), prioritize network calls and only cache when offline.

Key Tips to Avoid Headaches

  • Use ViewModels to retain loading states and cached data across configuration changes.
  • Add error handling for failed API calls—show a retry button and fall back to cache if available.
  • Test edge cases: Slow networks, sudden network drops, and large datasets to ensure smooth performance.

内容的提问来源于stack exchange,提问作者Parik Dhakan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:34:59