POC: Retain streambundle across fragment navigations

This commit is contained in:
Rahul Patel
2024-06-27 01:35:43 +05:30
committed by Aayush Gupta
parent c6850b176e
commit 2325b6e4a3
4 changed files with 63 additions and 45 deletions

View File

@@ -21,20 +21,20 @@ package com.aurora.store.data.model
sealed class ViewState {
object Loading : ViewState()
object Empty : ViewState()
data object Loading : ViewState()
data object Empty : ViewState()
data class Error(val error: String?) : ViewState()
data class Status(val status: String?) : ViewState()
data class Success<T>(val data: T) : ViewState()
}
sealed class AuthState {
object Available : AuthState()
object Unavailable : AuthState()
object SignedIn : AuthState()
object SignedOut : AuthState()
object Valid : AuthState()
object Fetching: AuthState()
object Verifying: AuthState()
data object Available : AuthState()
data object Unavailable : AuthState()
data object SignedIn : AuthState()
data object SignedOut : AuthState()
data object Valid : AuthState()
data object Fetching: AuthState()
data object Verifying: AuthState()
data class Failed(val status: String) : AuthState()
}

View File

@@ -21,7 +21,7 @@ package com.aurora.store.view.ui.commons
import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import androidx.fragment.app.activityViewModels
import com.aurora.Constants
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.StreamBundle
@@ -33,7 +33,7 @@ import com.aurora.store.data.model.ViewState
import com.aurora.store.databinding.FragmentForYouBinding
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
import com.aurora.store.view.epoxy.controller.GenericCarouselController
import com.aurora.store.viewmodel.homestream.BaseClusterViewModel
import com.aurora.store.viewmodel.homestream.StreamViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
@@ -43,7 +43,8 @@ class ForYouFragment : BaseFragment(R.layout.fragment_for_you),
private var _binding: FragmentForYouBinding? = null
private val binding get() = _binding!!
private val viewModel: BaseClusterViewModel by viewModels()
private var category: Category = Category.APPLICATION
private val viewModel: StreamViewModel by activityViewModels()
private lateinit var streamBundle: StreamBundle
@@ -71,35 +72,31 @@ class ForYouFragment : BaseFragment(R.layout.fragment_for_you),
}
when (pageType) {
0 -> viewModel.getStreamBundle(Category.APPLICATION, Type.HOME)
1 -> viewModel.getStreamBundle(Category.GAME, Type.HOME)
0 -> {
category = Category.APPLICATION
viewModel.getStreamBundle(category, Type.HOME)
}
1 -> {
category = Category.GAME
viewModel.getStreamBundle(category, Type.HOME)
}
}
binding.recycler.setController(genericCarouselController)
viewModel.liveData.observe(viewLifecycleOwner) {
when (it) {
is ViewState.Empty -> {
}
is ViewState.Loading -> {
genericCarouselController.setData(null)
}
is ViewState.Error -> {
}
is ViewState.Status -> {
}
is ViewState.Success<*> -> {
if (!::streamBundle.isInitialized) {
binding.recycler.addOnScrollListener(
object : EndlessRecyclerOnScrollListener() {
override fun onLoadMore(currentPage: Int) {
viewModel.observe()
viewModel.observe(category, Type.HOME)
}
}
)
@@ -108,6 +105,8 @@ class ForYouFragment : BaseFragment(R.layout.fragment_for_you),
streamBundle = it.data as StreamBundle
genericCarouselController.setData(streamBundle)
}
else -> {}
}
}
}
@@ -123,7 +122,7 @@ class ForYouFragment : BaseFragment(R.layout.fragment_for_you),
}
override fun onClusterScrolled(streamCluster: StreamCluster) {
viewModel.observeCluster(streamCluster)
viewModel.observeCluster(category, streamCluster)
}
override fun onAppClick(app: App) {

View File

@@ -42,7 +42,7 @@ import javax.inject.Inject
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class BaseClusterViewModel @Inject constructor(
class StreamViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val authProvider: AuthProvider
) : ViewModel() {
@@ -54,10 +54,11 @@ class BaseClusterViewModel @Inject constructor(
.using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<ViewState> = MutableLiveData()
var streamBundle: StreamBundle = StreamBundle()
lateinit var type: StreamContract.Type
lateinit var category: StreamContract.Category
private var streamBundleMap: MutableMap<StreamContract.Category, StreamBundle> = mutableMapOf(
StreamContract.Category.APPLICATION to StreamBundle(),
StreamContract.Category.GAME to StreamBundle()
)
fun contract(): StreamContract {
return if (authProvider.isAnonymous) {
@@ -68,33 +69,38 @@ class BaseClusterViewModel @Inject constructor(
}
fun getStreamBundle(category: StreamContract.Category, type: StreamContract.Type) {
this.type = type
this.category = category
liveData.postValue(ViewState.Loading)
observe()
observe(category, type)
}
fun observe() {
fun observe(category: StreamContract.Category, type: StreamContract.Type) {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
if (targetBundle(category).streamClusters.isNotEmpty()) {
liveData.postValue(ViewState.Success(targetBundle(category)))
}
try {
if (!streamBundle.hasCluster() || streamBundle.hasNext()) {
if (!targetBundle(category).hasCluster() || targetBundle(category).hasNext()) {
//Fetch new stream bundle
val newBundle = if (streamBundle.streamClusters.isEmpty()) {
val newBundle = if (targetBundle(category).streamClusters.isEmpty()) {
contract().fetch(type, category)
} else {
contract().nextStreamBundle(category, streamBundle.streamNextPageUrl)
contract().nextStreamBundle(
category,
targetBundle((category)).streamNextPageUrl
)
}
//Update old bundle
streamBundle.apply {
targetBundle(category).apply {
streamClusters.putAll(newBundle.streamClusters)
streamNextPageUrl = newBundle.streamNextPageUrl
}
//Post updated to UI
liveData.postValue(ViewState.Success(streamBundle))
liveData.postValue(ViewState.Success(targetBundle(category)))
} else {
Log.i("End of Bundle")
}
@@ -105,15 +111,15 @@ class BaseClusterViewModel @Inject constructor(
}
}
fun observeCluster(streamCluster: StreamCluster) {
fun observeCluster(category: StreamContract.Category, streamCluster: StreamCluster) {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
if (streamCluster.hasNext()) {
val newCluster =
contract().nextStreamCluster(streamCluster.clusterNextPageUrl)
updateCluster(streamCluster.id, newCluster)
liveData.postValue(ViewState.Success(streamBundle))
updateCluster(category, streamCluster.id, newCluster)
liveData.postValue(ViewState.Success(targetBundle(category)))
} else {
Log.i("End of cluster")
streamCluster.clusterNextPageUrl = String()
@@ -125,10 +131,20 @@ class BaseClusterViewModel @Inject constructor(
}
}
private fun updateCluster(clusterID: Int, newCluster: StreamCluster) {
streamBundle.streamClusters[clusterID]?.apply {
private fun updateCluster(
category: StreamContract.Category,
clusterID: Int,
newCluster: StreamCluster
) {
targetBundle(category).streamClusters[clusterID]?.apply {
clusterAppList.addAll(newCluster.clusterAppList)
clusterNextPageUrl = newCluster.clusterNextPageUrl
}
}
private fun targetBundle(category: StreamContract.Category): StreamBundle {
val streamBundle = streamBundleMap[category]
return streamBundle!!
}
}