Experiment with stashing

This commit is contained in:
Rahul Patel
2024-06-29 02:53:18 +05:30
committed by Aayush Gupta
parent 2325b6e4a3
commit 6f425fe641
6 changed files with 123 additions and 57 deletions

View File

@@ -0,0 +1,9 @@
package com.aurora.store
import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.contracts.StreamContract
import com.aurora.gplayapi.helpers.contracts.TopChartsContract
typealias TopChartStash = Map<TopChartsContract.Type, Map<TopChartsContract.Chart, StreamCluster>>
typealias HomeStash = MutableMap<StreamContract.Category, StreamBundle>

View File

@@ -21,6 +21,10 @@ package com.aurora.store.data.model
sealed class ViewState { sealed class ViewState {
inline fun <reified T> ViewState.getDataAs(): T {
return (this as? Success<*>)?.data as T
}
data object Loading : ViewState() data object Loading : ViewState()
data object Empty : ViewState() data object Empty : ViewState()
data class Error(val error: String?) : ViewState() data class Error(val error: String?) : ViewState()
@@ -34,7 +38,7 @@ sealed class AuthState {
data object SignedIn : AuthState() data object SignedIn : AuthState()
data object SignedOut : AuthState() data object SignedOut : AuthState()
data object Valid : AuthState() data object Valid : AuthState()
data object Fetching: AuthState() data object Fetching : AuthState()
data object Verifying: AuthState() data object Verifying : AuthState()
data class Failed(val status: String) : AuthState() data class Failed(val status: String) : AuthState()
} }

View File

@@ -28,8 +28,10 @@ import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.contracts.StreamContract.Category import com.aurora.gplayapi.helpers.contracts.StreamContract.Category
import com.aurora.gplayapi.helpers.contracts.StreamContract.Type import com.aurora.gplayapi.helpers.contracts.StreamContract.Type
import com.aurora.store.HomeStash
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.data.model.ViewState import com.aurora.store.data.model.ViewState
import com.aurora.store.data.ViewState.Loading.getDataAs
import com.aurora.store.databinding.FragmentForYouBinding import com.aurora.store.databinding.FragmentForYouBinding
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
import com.aurora.store.view.epoxy.controller.GenericCarouselController import com.aurora.store.view.epoxy.controller.GenericCarouselController
@@ -43,10 +45,10 @@ class ForYouFragment : BaseFragment(R.layout.fragment_for_you),
private var _binding: FragmentForYouBinding? = null private var _binding: FragmentForYouBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private var category: Category = Category.APPLICATION
private val viewModel: StreamViewModel by activityViewModels() private val viewModel: StreamViewModel by activityViewModels()
private lateinit var streamBundle: StreamBundle private var category: Category = Category.APPLICATION
private var streamBundle: StreamBundle? = StreamBundle()
companion object { companion object {
@JvmStatic @JvmStatic
@@ -84,6 +86,13 @@ class ForYouFragment : BaseFragment(R.layout.fragment_for_you),
} }
binding.recycler.setController(genericCarouselController) binding.recycler.setController(genericCarouselController)
binding.recycler.addOnScrollListener(
object : EndlessRecyclerOnScrollListener() {
override fun onLoadMore(currentPage: Int) {
viewModel.observe(category, Type.HOME)
}
}
)
viewModel.liveData.observe(viewLifecycleOwner) { viewModel.liveData.observe(viewLifecycleOwner) {
when (it) { when (it) {
@@ -92,17 +101,9 @@ class ForYouFragment : BaseFragment(R.layout.fragment_for_you),
} }
is ViewState.Success<*> -> { is ViewState.Success<*> -> {
if (!::streamBundle.isInitialized) { val stash = it.getDataAs<HomeStash>()
binding.recycler.addOnScrollListener( streamBundle = stash[category]
object : EndlessRecyclerOnScrollListener() {
override fun onLoadMore(currentPage: Int) {
viewModel.observe(category, Type.HOME)
}
}
)
}
streamBundle = it.data as StreamBundle
genericCarouselController.setData(streamBundle) genericCarouselController.setData(streamBundle)
} }

View File

@@ -21,12 +21,15 @@ package com.aurora.store.view.ui.commons
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.fragment.app.viewModels import androidx.fragment.app.activityViewModels
import com.aurora.Constants import com.aurora.Constants
import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.contracts.TopChartsContract.Chart import com.aurora.gplayapi.helpers.contracts.TopChartsContract.Chart
import com.aurora.gplayapi.helpers.contracts.TopChartsContract.Type import com.aurora.gplayapi.helpers.contracts.TopChartsContract.Type
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.TopChartStash
import com.aurora.store.data.ViewState
import com.aurora.store.data.ViewState.Empty.getDataAs
import com.aurora.store.databinding.FragmentTopContainerBinding import com.aurora.store.databinding.FragmentTopContainerBinding
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
import com.aurora.store.view.epoxy.views.AppProgressViewModel_ import com.aurora.store.view.epoxy.views.AppProgressViewModel_
@@ -41,7 +44,9 @@ class TopChartFragment : BaseFragment(R.layout.fragment_top_container) {
private var _binding: FragmentTopContainerBinding? = null private var _binding: FragmentTopContainerBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private val viewModel: TopChartViewModel by viewModels() private val viewModel: TopChartViewModel by activityViewModels()
private var streamCluster: StreamCluster? = StreamCluster()
companion object { companion object {
@JvmStatic @JvmStatic
@@ -59,42 +64,52 @@ class TopChartFragment : BaseFragment(R.layout.fragment_top_container) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
_binding = FragmentTopContainerBinding.bind(view) _binding = FragmentTopContainerBinding.bind(view)
var chartType = 0 var type = 0
var chartCategory = 0 var category = 0
val bundle = arguments val bundle = arguments
if (bundle != null) { if (bundle != null) {
chartType = bundle.getInt(Constants.TOP_CHART_TYPE, 0) type = bundle.getInt(Constants.TOP_CHART_TYPE, 0)
chartCategory = bundle.getInt(Constants.TOP_CHART_CATEGORY, 0) category = bundle.getInt(Constants.TOP_CHART_CATEGORY, 0)
} }
when (chartType) { val chartType = when (type) {
0 -> when (chartCategory) { 1 -> Type.GAME
0 -> viewModel.getStreamCluster(Type.APPLICATION, Chart.TOP_SELLING_FREE) else -> Type.APPLICATION
1 -> viewModel.getStreamCluster(Type.APPLICATION, Chart.TOP_GROSSING)
2 -> viewModel.getStreamCluster(Type.APPLICATION, Chart.MOVERS_SHAKERS)
3 -> viewModel.getStreamCluster(Type.APPLICATION, Chart.TOP_SELLING_PAID)
}
1 -> when (chartCategory) {
0 -> viewModel.getStreamCluster(Type.GAME, Chart.TOP_SELLING_FREE)
1 -> viewModel.getStreamCluster(Type.GAME, Chart.TOP_GROSSING)
2 -> viewModel.getStreamCluster(Type.GAME, Chart.MOVERS_SHAKERS)
3 -> viewModel.getStreamCluster(Type.GAME, Chart.TOP_SELLING_PAID)
}
} }
viewModel.liveData.observe(viewLifecycleOwner) { val chartCategory = when (category) {
updateController(it) 1 -> Chart.TOP_GROSSING
2 -> Chart.MOVERS_SHAKERS
3 -> Chart.TOP_SELLING_PAID
else -> Chart.TOP_SELLING_FREE
} }
binding.recycler.addOnScrollListener(object : EndlessRecyclerOnScrollListener() { binding.recycler.addOnScrollListener(object : EndlessRecyclerOnScrollListener() {
override fun onLoadMore(currentPage: Int) { override fun onLoadMore(currentPage: Int) {
viewModel.nextCluster() viewModel.nextCluster(chartType, chartCategory)
} }
}) })
updateController(null) updateController(null)
viewModel.getStreamCluster(chartType, chartCategory)
viewModel.liveData.observe(viewLifecycleOwner) {
when (it) {
is ViewState.Loading, is ViewState.Error -> {
updateController(null)
}
is ViewState.Success<*> -> {
val stash = it.getDataAs<TopChartStash>()
streamCluster = stash[chartType]?.get(chartCategory)
updateController(streamCluster)
}
else -> {}
}
}
} }
override fun onDestroyView() { override fun onDestroyView() {

View File

@@ -30,6 +30,7 @@ import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.data.model.ViewState import com.aurora.store.data.model.ViewState
import com.aurora.gplayapi.helpers.contracts.StreamContract import com.aurora.gplayapi.helpers.contracts.StreamContract
import com.aurora.gplayapi.helpers.web.WebStreamHelper import com.aurora.gplayapi.helpers.web.WebStreamHelper
import com.aurora.store.HomeStash
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log import com.aurora.store.util.Log
@@ -55,7 +56,7 @@ class StreamViewModel @Inject constructor(
val liveData: MutableLiveData<ViewState> = MutableLiveData() val liveData: MutableLiveData<ViewState> = MutableLiveData()
private var streamBundleMap: MutableMap<StreamContract.Category, StreamBundle> = mutableMapOf( private var stash: HomeStash = mutableMapOf(
StreamContract.Category.APPLICATION to StreamBundle(), StreamContract.Category.APPLICATION to StreamBundle(),
StreamContract.Category.GAME to StreamBundle() StreamContract.Category.GAME to StreamBundle()
) )
@@ -77,7 +78,7 @@ class StreamViewModel @Inject constructor(
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
if (targetBundle(category).streamClusters.isNotEmpty()) { if (targetBundle(category).streamClusters.isNotEmpty()) {
liveData.postValue(ViewState.Success(targetBundle(category))) liveData.postValue(ViewState.Success(stash))
} }
try { try {
@@ -100,7 +101,7 @@ class StreamViewModel @Inject constructor(
} }
//Post updated to UI //Post updated to UI
liveData.postValue(ViewState.Success(targetBundle(category))) liveData.postValue(ViewState.Success(stash))
} else { } else {
Log.i("End of Bundle") Log.i("End of Bundle")
} }
@@ -119,7 +120,7 @@ class StreamViewModel @Inject constructor(
val newCluster = val newCluster =
contract().nextStreamCluster(streamCluster.clusterNextPageUrl) contract().nextStreamCluster(streamCluster.clusterNextPageUrl)
updateCluster(category, streamCluster.id, newCluster) updateCluster(category, streamCluster.id, newCluster)
liveData.postValue(ViewState.Success(targetBundle(category))) liveData.postValue(ViewState.Success(stash))
} else { } else {
Log.i("End of cluster") Log.i("End of cluster")
streamCluster.clusterNextPageUrl = String() streamCluster.clusterNextPageUrl = String()
@@ -143,8 +144,9 @@ class StreamViewModel @Inject constructor(
} }
private fun targetBundle(category: StreamContract.Category): StreamBundle { private fun targetBundle(category: StreamContract.Category): StreamBundle {
val streamBundle = streamBundleMap[category] // stash is initialized with empty StreamBundle so this will never return null or throw an exception
val streamBundle = stash.getValue(category)
return streamBundle!! return streamBundle
} }
} }

View File

@@ -28,6 +28,8 @@ import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.TopChartsHelper import com.aurora.gplayapi.helpers.TopChartsHelper
import com.aurora.gplayapi.helpers.contracts.TopChartsContract import com.aurora.gplayapi.helpers.contracts.TopChartsContract
import com.aurora.gplayapi.helpers.web.WebTopChartsHelper import com.aurora.gplayapi.helpers.web.WebTopChartsHelper
import com.aurora.store.TopChartStash
import com.aurora.store.data.ViewState
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider import com.aurora.store.data.providers.AuthProvider
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@@ -50,11 +52,22 @@ class TopChartViewModel @Inject constructor(
private val webTopChartsHelper: TopChartsContract = WebTopChartsHelper() private val webTopChartsHelper: TopChartsContract = WebTopChartsHelper()
.using(HttpClient.getPreferredClient(context)) .using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<StreamCluster> = MutableLiveData() private val dummyChart = mapOf(
var streamCluster: StreamCluster = StreamCluster() TopChartsContract.Chart.TOP_GROSSING to StreamCluster(),
TopChartsContract.Chart.TOP_SELLING_FREE to StreamCluster(),
TopChartsContract.Chart.TOP_SELLING_PAID to StreamCluster(),
TopChartsContract.Chart.MOVERS_SHAKERS to StreamCluster(),
)
private var stash: TopChartStash = mapOf(
TopChartsContract.Type.APPLICATION to dummyChart,
TopChartsContract.Type.GAME to dummyChart
)
val liveData: MutableLiveData<ViewState> = MutableLiveData()
private fun contract(): TopChartsContract { private fun contract(): TopChartsContract {
return if(authProvider.isAnonymous){ return if (authProvider.isAnonymous) {
webTopChartsHelper webTopChartsHelper
} else { } else {
topChartsHelper topChartsHelper
@@ -63,34 +76,56 @@ class TopChartViewModel @Inject constructor(
fun getStreamCluster(type: TopChartsContract.Type, chart: TopChartsContract.Chart) { fun getStreamCluster(type: TopChartsContract.Type, chart: TopChartsContract.Chart) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
if (targetCluster(type, chart).clusterAppList.isNotEmpty()) {
liveData.postValue(ViewState.Success(stash))
}
try { try {
streamCluster = contract().getCluster(type.value, chart.value) val cluster = contract().getCluster(type.value, chart.value)
liveData.postValue(streamCluster) updateCluster(type, chart, cluster)
liveData.postValue(ViewState.Success(stash))
} catch (_: Exception) { } catch (_: Exception) {
} }
} }
} }
fun nextCluster() { fun nextCluster(type: TopChartsContract.Type, chart: TopChartsContract.Chart) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
try { try {
if (streamCluster.hasNext()) { val target = targetCluster(type, chart)
if (target.hasNext()) {
val newCluster = topChartsHelper.getNextStreamCluster( val newCluster = topChartsHelper.getNextStreamCluster(
streamCluster.clusterNextPageUrl target.clusterNextPageUrl
) )
streamCluster.apply { updateCluster(type, chart, newCluster)
clusterAppList.addAll(newCluster.clusterAppList)
clusterNextPageUrl = newCluster.clusterNextPageUrl
}
liveData.postValue(streamCluster) liveData.postValue(ViewState.Success(stash))
} else {
} }
} catch (_: Exception) { } catch (_: Exception) {
} }
} }
} }
} }
private fun updateCluster(
type: TopChartsContract.Type,
chart: TopChartsContract.Chart,
newCluster: StreamCluster
) {
targetCluster(type, chart).apply {
clusterAppList.addAll(newCluster.clusterAppList)
clusterNextPageUrl = newCluster.clusterNextPageUrl
}
}
private fun targetCluster(
type: TopChartsContract.Type,
chart: TopChartsContract.Chart
): StreamCluster {
// Stash is initialized with empty clusters so this will never return null or throw an exception
val cluster = stash.getValue(type).getValue(chart)
return cluster
}
} }