Fix app stream & cluster state issues
This commit is contained in:
@@ -58,9 +58,9 @@ class StreamBrowseFragment : BaseFragment<FragmentGenericWithToolbarBinding>() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
viewModel.initCluster(streamCluster)
|
viewModel.seedCluster(streamCluster)
|
||||||
viewModel.liveData.observe(viewLifecycleOwner) {
|
viewModel.liveData.observe(viewLifecycleOwner) {
|
||||||
updateController(streamCluster)
|
updateController(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import com.aurora.gplayapi.helpers.web.WebStreamHelper
|
|||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.supervisorScope
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@@ -42,31 +41,27 @@ class StreamBrowseViewModel @Inject constructor(
|
|||||||
|
|
||||||
private lateinit var streamCluster: StreamCluster
|
private lateinit var streamCluster: StreamCluster
|
||||||
|
|
||||||
fun initCluster(cluster: StreamCluster) {
|
fun seedCluster(cluster: StreamCluster) {
|
||||||
streamCluster = cluster
|
streamCluster = cluster
|
||||||
liveData.postValue(streamCluster)
|
liveData.postValue(streamCluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nextCluster() {
|
fun nextCluster() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
supervisorScope {
|
try {
|
||||||
try {
|
if (streamCluster.hasNext()) {
|
||||||
if (streamCluster.hasNext()) {
|
val next = streamHelper.nextStreamCluster(streamCluster.clusterNextPageUrl)
|
||||||
val nextCluster = streamHelper.nextStreamCluster(
|
|
||||||
streamCluster.clusterNextPageUrl
|
|
||||||
)
|
|
||||||
|
|
||||||
streamCluster = streamCluster.copy(
|
streamCluster = streamCluster.copy(
|
||||||
clusterNextPageUrl = nextCluster.clusterNextPageUrl,
|
clusterNextPageUrl = next.clusterNextPageUrl,
|
||||||
clusterAppList = streamCluster.clusterAppList + nextCluster.clusterAppList
|
clusterAppList = streamCluster.clusterAppList + next.clusterAppList
|
||||||
)
|
)
|
||||||
|
|
||||||
liveData.postValue(streamCluster)
|
liveData.postValue(streamCluster)
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "End of Cluster")
|
Log.i(TAG, "End of Cluster")
|
||||||
}
|
|
||||||
} catch (_: Exception) {
|
|
||||||
}
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,9 +107,9 @@ class DetailsClusterViewModel @Inject constructor(
|
|||||||
clusterNextPageUrl = newCluster.clusterNextPageUrl,
|
clusterNextPageUrl = newCluster.clusterNextPageUrl,
|
||||||
clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList
|
clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList
|
||||||
)
|
)
|
||||||
val newStreamClusters = bundle.streamClusters.toMutableMap().also {
|
|
||||||
it.remove(clusterID)
|
val newStreamClusters = bundle.streamClusters.toMutableMap().apply {
|
||||||
it[clusterID] = mergedCluster
|
this[clusterID] = mergedCluster
|
||||||
}
|
}
|
||||||
|
|
||||||
stash.put(url, bundle.copy(streamClusters = newStreamClusters))
|
stash.put(url, bundle.copy(streamClusters = newStreamClusters))
|
||||||
|
|||||||
@@ -90,9 +90,9 @@ class DevProfileViewModel @Inject constructor(
|
|||||||
clusterNextPageUrl = newCluster.clusterNextPageUrl,
|
clusterNextPageUrl = newCluster.clusterNextPageUrl,
|
||||||
clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList
|
clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList
|
||||||
)
|
)
|
||||||
val newStreamClusters = streamBundle.streamClusters.toMutableMap().also {
|
|
||||||
it.remove(newCluster.id)
|
val newStreamClusters = streamBundle.streamClusters.toMutableMap().apply {
|
||||||
it[newCluster.id] = mergedCluster
|
this[newCluster.id] = mergedCluster
|
||||||
}
|
}
|
||||||
|
|
||||||
streamBundle = streamBundle.copy(streamClusters = newStreamClusters)
|
streamBundle = streamBundle.copy(streamClusters = newStreamClusters)
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ import com.aurora.store.data.model.ViewState
|
|||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.supervisorScope
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@@ -44,11 +45,14 @@ class StreamViewModel @Inject constructor(
|
|||||||
|
|
||||||
val liveData: MutableLiveData<ViewState> = MutableLiveData()
|
val liveData: MutableLiveData<ViewState> = MutableLiveData()
|
||||||
|
|
||||||
private var stash: HomeStash = mutableMapOf()
|
private val stash: HomeStash = mutableMapOf()
|
||||||
|
|
||||||
private val streamContract: StreamContract
|
private val streamContract: StreamContract
|
||||||
get() = webStreamHelper
|
get() = webStreamHelper
|
||||||
|
|
||||||
|
// Mutex to protect stash access for thread safety
|
||||||
|
private val stashMutex = Mutex()
|
||||||
|
|
||||||
fun getStreamBundle(category: StreamContract.Category, type: StreamContract.Type) {
|
fun getStreamBundle(category: StreamContract.Category, type: StreamContract.Type) {
|
||||||
liveData.postValue(ViewState.Loading)
|
liveData.postValue(ViewState.Loading)
|
||||||
observe(category, type)
|
observe(category, type)
|
||||||
@@ -56,16 +60,18 @@ class StreamViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun observe(category: StreamContract.Category, type: StreamContract.Type) {
|
fun observe(category: StreamContract.Category, type: StreamContract.Type) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
supervisorScope {
|
try {
|
||||||
val bundle = targetBundle(category)
|
stashMutex.withLock {
|
||||||
if (bundle.hasCluster()) {
|
val bundle = targetBundle(category)
|
||||||
liveData.postValue(ViewState.Success(stash))
|
|
||||||
}
|
// Post existing data if any clusters exist
|
||||||
|
if (bundle.hasCluster()) {
|
||||||
|
liveData.postValue(ViewState.Success(stash.toMap()))
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
if (!bundle.hasCluster() || bundle.hasNext()) {
|
if (!bundle.hasCluster() || bundle.hasNext()) {
|
||||||
|
|
||||||
//Fetch new stream bundle
|
// Fetch new stream bundle
|
||||||
val newBundle = if (bundle.hasCluster()) {
|
val newBundle = if (bundle.hasCluster()) {
|
||||||
streamContract.nextStreamBundle(
|
streamContract.nextStreamBundle(
|
||||||
category,
|
category,
|
||||||
@@ -75,41 +81,43 @@ class StreamViewModel @Inject constructor(
|
|||||||
streamContract.fetch(type, category)
|
streamContract.fetch(type, category)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Update old bundle
|
// Update old bundle
|
||||||
val mergedBundle = bundle.copy(
|
val mergedBundle = bundle.copy(
|
||||||
streamClusters = bundle.streamClusters + newBundle.streamClusters,
|
streamClusters = bundle.streamClusters + newBundle.streamClusters,
|
||||||
streamNextPageUrl = newBundle.streamNextPageUrl
|
streamNextPageUrl = newBundle.streamNextPageUrl
|
||||||
)
|
)
|
||||||
stash[category] = mergedBundle
|
stash[category] = mergedBundle
|
||||||
|
|
||||||
//Post updated to UI
|
// Post updated to UI
|
||||||
liveData.postValue(ViewState.Success(stash))
|
liveData.postValue(ViewState.Success(stash.toMap()))
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "End of Bundle")
|
Log.i(TAG, "End of Bundle")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
liveData.postValue(ViewState.Error(e.message))
|
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
liveData.postValue(ViewState.Error(e.message))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun observeCluster(category: StreamContract.Category, streamCluster: StreamCluster) {
|
fun observeCluster(category: StreamContract.Category, streamCluster: StreamCluster) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
supervisorScope {
|
try {
|
||||||
try {
|
if (streamCluster.hasNext()) {
|
||||||
if (streamCluster.hasNext()) {
|
val newCluster = streamContract.nextStreamCluster(
|
||||||
val newCluster = streamContract.nextStreamCluster(
|
streamCluster.clusterNextPageUrl
|
||||||
streamCluster.clusterNextPageUrl
|
)
|
||||||
)
|
|
||||||
|
stashMutex.withLock {
|
||||||
updateCluster(category, streamCluster.id, newCluster)
|
updateCluster(category, streamCluster.id, newCluster)
|
||||||
liveData.postValue(ViewState.Success(stash))
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, "End of cluster")
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
liveData.postValue(ViewState.Error(e.message))
|
liveData.postValue(ViewState.Success(stash.toMap()))
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "End of cluster ${streamCluster.id}")
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
liveData.postValue(ViewState.Error(e.message))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,26 +127,22 @@ class StreamViewModel @Inject constructor(
|
|||||||
clusterID: Int,
|
clusterID: Int,
|
||||||
newCluster: StreamCluster
|
newCluster: StreamCluster
|
||||||
) {
|
) {
|
||||||
val bundle = targetBundle(category)
|
val bundle = stash[category] ?: return
|
||||||
bundle.streamClusters[clusterID]?.let { oldCluster ->
|
val oldCluster = bundle.streamClusters[clusterID] ?: return
|
||||||
val mergedCluster = oldCluster.copy(
|
|
||||||
clusterNextPageUrl = newCluster.clusterNextPageUrl,
|
|
||||||
clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList
|
|
||||||
)
|
|
||||||
val newStreamClusters = bundle.streamClusters.toMutableMap().also {
|
|
||||||
it.remove(clusterID)
|
|
||||||
it[clusterID] = mergedCluster
|
|
||||||
}
|
|
||||||
|
|
||||||
stash.put(category, bundle.copy(streamClusters = newStreamClusters))
|
val mergedCluster = oldCluster.copy(
|
||||||
|
clusterNextPageUrl = newCluster.clusterNextPageUrl,
|
||||||
|
clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList
|
||||||
|
)
|
||||||
|
|
||||||
|
val updatedClusters = bundle.streamClusters.toMutableMap().apply {
|
||||||
|
this[clusterID] = mergedCluster
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stash[category] = bundle.copy(streamClusters = updatedClusters)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun targetBundle(category: StreamContract.Category): StreamBundle {
|
private fun targetBundle(category: StreamContract.Category): StreamBundle {
|
||||||
val streamBundle = stash.getOrPut(category) {
|
return stash.getOrPut(category) { StreamBundle() }
|
||||||
StreamBundle()
|
|
||||||
}
|
|
||||||
|
|
||||||
return streamBundle
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package com.aurora.store.viewmodel.review
|
package com.aurora.store.viewmodel.review
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
@@ -28,13 +29,13 @@ import com.aurora.gplayapi.helpers.ReviewsHelper
|
|||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.supervisorScope
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ReviewViewModel @Inject constructor(
|
class ReviewViewModel @Inject constructor(
|
||||||
private val reviewsHelper: ReviewsHelper
|
private val reviewsHelper: ReviewsHelper
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
val TAG = javaClass.simpleName
|
||||||
|
|
||||||
val liveData: MutableLiveData<ReviewCluster> = MutableLiveData()
|
val liveData: MutableLiveData<ReviewCluster> = MutableLiveData()
|
||||||
|
|
||||||
@@ -42,29 +43,29 @@ class ReviewViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun fetchReview(packageName: String, filter: Review.Filter) {
|
fun fetchReview(packageName: String, filter: Review.Filter) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
supervisorScope {
|
try {
|
||||||
try {
|
reviewsCluster = reviewsHelper.getReviews(packageName, filter)
|
||||||
reviewsCluster = reviewsHelper.getReviews(packageName, filter)
|
liveData.postValue(reviewsCluster)
|
||||||
liveData.postValue(reviewsCluster)
|
} catch (e: Exception) {
|
||||||
} catch (_: Exception) {
|
Log.e(TAG, "Failed to fetch reviews", e)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun next(nextReviewPageUrl: String) {
|
fun next(nextReviewPageUrl: String) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
supervisorScope {
|
try {
|
||||||
try {
|
val currentCluster = reviewsCluster
|
||||||
val nextReviewCluster = reviewsHelper.next(nextReviewPageUrl)
|
val nextReviewCluster = reviewsHelper.next(nextReviewPageUrl)
|
||||||
reviewsCluster = reviewsCluster.copy(
|
|
||||||
nextPageUrl = nextReviewCluster.nextPageUrl,
|
|
||||||
reviewList = nextReviewCluster.reviewList + nextReviewCluster.reviewList
|
|
||||||
)
|
|
||||||
|
|
||||||
liveData.postValue(reviewsCluster)
|
reviewsCluster = currentCluster.copy(
|
||||||
} catch (_: Exception) {
|
nextPageUrl = nextReviewCluster.nextPageUrl,
|
||||||
}
|
reviewList = currentCluster.reviewList + nextReviewCluster.reviewList
|
||||||
|
)
|
||||||
|
|
||||||
|
liveData.postValue(reviewsCluster)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to fetch next reviews $nextReviewPageUrl", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,9 +121,9 @@ class CategoryStreamViewModel @Inject constructor(
|
|||||||
clusterNextPageUrl = newCluster.clusterNextPageUrl,
|
clusterNextPageUrl = newCluster.clusterNextPageUrl,
|
||||||
clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList
|
clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList
|
||||||
)
|
)
|
||||||
val newStreamClusters = bundle.streamClusters.toMutableMap().also {
|
|
||||||
it.remove(clusterID)
|
val newStreamClusters = bundle.streamClusters.toMutableMap().apply {
|
||||||
it[clusterID] = mergedCluster
|
this[clusterID] = mergedCluster
|
||||||
}
|
}
|
||||||
|
|
||||||
stash.put(browseUrl, bundle.copy(streamClusters = newStreamClusters))
|
stash.put(browseUrl, bundle.copy(streamClusters = newStreamClusters))
|
||||||
|
|||||||
Reference in New Issue
Block a user