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

3
.gitignore vendored
View File

@@ -44,3 +44,6 @@ app/nightly/*
#Exclude signing configurations & keystore #Exclude signing configurations & keystore
signing.properties signing.properties
*.keystore *.keystore
.kotlin/*

View File

@@ -21,20 +21,20 @@ package com.aurora.store.data.model
sealed class ViewState { sealed class ViewState {
object Loading : ViewState() data object Loading : ViewState()
object Empty : ViewState() data object Empty : ViewState()
data class Error(val error: String?) : ViewState() data class Error(val error: String?) : ViewState()
data class Status(val status: String?) : ViewState() data class Status(val status: String?) : ViewState()
data class Success<T>(val data: T) : ViewState() data class Success<T>(val data: T) : ViewState()
} }
sealed class AuthState { sealed class AuthState {
object Available : AuthState() data object Available : AuthState()
object Unavailable : AuthState() data object Unavailable : AuthState()
object SignedIn : AuthState() data object SignedIn : AuthState()
object SignedOut : AuthState() data object SignedOut : AuthState()
object Valid : AuthState() data object Valid : AuthState()
object Fetching: AuthState() data object Fetching: AuthState()
object Verifying: AuthState() data object Verifying: AuthState()
data class Failed(val status: String) : 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.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.App import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.StreamBundle 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.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
import com.aurora.store.viewmodel.homestream.BaseClusterViewModel import com.aurora.store.viewmodel.homestream.StreamViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
@@ -43,7 +43,8 @@ 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 val viewModel: BaseClusterViewModel by viewModels() private var category: Category = Category.APPLICATION
private val viewModel: StreamViewModel by activityViewModels()
private lateinit var streamBundle: StreamBundle private lateinit var streamBundle: StreamBundle
@@ -71,35 +72,31 @@ class ForYouFragment : BaseFragment(R.layout.fragment_for_you),
} }
when (pageType) { when (pageType) {
0 -> viewModel.getStreamBundle(Category.APPLICATION, Type.HOME) 0 -> {
1 -> viewModel.getStreamBundle(Category.GAME, Type.HOME) category = Category.APPLICATION
viewModel.getStreamBundle(category, Type.HOME)
}
1 -> {
category = Category.GAME
viewModel.getStreamBundle(category, Type.HOME)
}
} }
binding.recycler.setController(genericCarouselController) binding.recycler.setController(genericCarouselController)
viewModel.liveData.observe(viewLifecycleOwner) { viewModel.liveData.observe(viewLifecycleOwner) {
when (it) { when (it) {
is ViewState.Empty -> {
}
is ViewState.Loading -> { is ViewState.Loading -> {
genericCarouselController.setData(null) genericCarouselController.setData(null)
} }
is ViewState.Error -> {
}
is ViewState.Status -> {
}
is ViewState.Success<*> -> { is ViewState.Success<*> -> {
if (!::streamBundle.isInitialized) { if (!::streamBundle.isInitialized) {
binding.recycler.addOnScrollListener( binding.recycler.addOnScrollListener(
object : EndlessRecyclerOnScrollListener() { object : EndlessRecyclerOnScrollListener() {
override fun onLoadMore(currentPage: Int) { 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 streamBundle = it.data as StreamBundle
genericCarouselController.setData(streamBundle) genericCarouselController.setData(streamBundle)
} }
else -> {}
} }
} }
} }
@@ -123,7 +122,7 @@ class ForYouFragment : BaseFragment(R.layout.fragment_for_you),
} }
override fun onClusterScrolled(streamCluster: StreamCluster) { override fun onClusterScrolled(streamCluster: StreamCluster) {
viewModel.observeCluster(streamCluster) viewModel.observeCluster(category, streamCluster)
} }
override fun onAppClick(app: App) { override fun onAppClick(app: App) {

View File

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