Skip to content

Instantly share code, notes, and snippets.

@Sardorbekcyber
Created October 20, 2023 11:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sardorbekcyber/1765a5b467bc62b432999207c125029a to your computer and use it in GitHub Desktop.
Save Sardorbekcyber/1765a5b467bc62b432999207c125029a to your computer and use it in GitHub Desktop.
Follower ViewModel for Code Example
package app.skylab.profile.ui.fragments.followers
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import app.skylab.core.ActionDispatcher
import app.skylab.core.InAppAction
import app.skylab.core.coroutines.CoroutineDispatchers
import app.skylab.core.status.FollowStatus
import app.skylab.feature.auth.api.AccountSource
import app.skylab.feature.profile.model.AccountFollowerUi
import app.skylab.feature.profile.usecase.FollowUseCase
import app.skylab.feature.profile.usecase.RemoveFollowerUseCase
import app.skylab.feature.profile.usecase.TotalPendingFollowersUseCase
import app.skylab.feature.profile.usecase.UnfollowUsecase
import app.skylab.feature.profile.usecase.paging.FollowersPagingSource
import app.skylab.network.Try
import app.skylab.profile.ui.fragments.followers.FollowersContract.Intent
import app.skylab.profile.ui.fragments.followers.FollowersContract.Router
import app.skylab.profile.ui.fragments.followers.FollowersContract.SideEffect
import app.skylab.profile.ui.fragments.followers.FollowersContract.State
import dagger.Lazy
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import org.orbitmvi.orbit.syntax.simple.intent
import org.orbitmvi.orbit.syntax.simple.postSideEffect
import org.orbitmvi.orbit.syntax.simple.reduce
import org.orbitmvi.orbit.syntax.simple.repeatOnSubscription
import org.orbitmvi.orbit.viewmodel.container
import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class)
@HiltViewModel
class FollowersViewModel @Inject constructor(
private val router: Router,
private val removeFollowerUseCase: Lazy<RemoveFollowerUseCase>,
private val totalPendingFollowersUseCase: Lazy<TotalPendingFollowersUseCase>,
private val unfollowUsecase: Lazy<UnfollowUsecase>,
private val followUseCase: Lazy<FollowUseCase>,
private val followersFactory: FollowersPagingSource.Factory,
private val actionDispatcher: ActionDispatcher,
private val dispatchers: CoroutineDispatchers,
accountSource: AccountSource,
savedStateHandle: SavedStateHandle
) : ViewModel(), FollowersContract.ViewModel {
private val accountId = savedStateHandle[ARG_ACCOUNT_ID] ?: accountSource.accountId
private val showBack = savedStateHandle["SHOW_BACK"] ?: false
private val searchQuery: MutableSharedFlow<String> = MutableSharedFlow()
override val container = container<State, SideEffect>(
State(
accountId = accountId,
isSelf = accountId == accountSource.accountId,
showBack = showBack
), savedStateHandle
) {
getTotalPendingCount()
listenActions()
}
override val followers: Flow<PagingData<AccountFollowerUi>>
init {
followers = searchQuery
.onStart { emit("") }
.distinctUntilChanged()
.flatMapLatest { provideFollowers(it) }
.cachedIn(viewModelScope)
}
override fun dispatch(intent: Intent) {
viewModelScope.launch {
when (intent) {
Intent.PendingRequestsClick -> router.navigateToPending()
Intent.BackClick -> router.navigateBack()
is Intent.ProfileClick -> router.navigateToProfile(intent.accountId)
is Intent.RemoveClick -> remove(intent.accountId)
is Intent.SearchChanged -> searchQuery.emit(intent.query)
is Intent.FollowClick -> followClick(intent.account)
}
}
}
private fun provideFollowers(searchText: String) =
Pager(
config = PagingConfig(
pageSize = ITEMS_PER_PAGE,
enablePlaceholders = false,
initialLoadSize = ITEMS_PER_PAGE,
prefetchDistance = ITEMS_PER_PAGE,
),
pagingSourceFactory = { followersFactory.create(accountId, searchText) }
).flow
private fun listenActions() = intent(registerIdling = false) {
repeatOnSubscription {
viewModelScope.launch(dispatchers.IO) {
actionDispatcher.actions.collect { actions ->
when (actions) {
InAppAction.FOLLOW_CHANGED -> postSideEffect(SideEffect.RefreshGeneralAdapter)
}
}
}
}
}
private fun getTotalPendingCount() = intent {
viewModelScope.launch {
if (state.isSelf) {
when (val response = totalPendingFollowersUseCase.get()(accountId)) {
is Try.Failure -> postSideEffect(SideEffect.ServerError(response.exception.message))
is Try.Success -> reduce { state.copy(pendingCount = response.data) }
}
}
}
}
private fun followClick(account: AccountFollowerUi) = intent {
when (account.followStatus) {
FollowStatus.FOLLOWER -> unfollow(account.accountId)
FollowStatus.FOLLOW_REQUESTED -> postSideEffect(SideEffect.FollowRequested)
FollowStatus.NOT_FOLLOWER -> follow(account.accountId)
}
}
private fun remove(accountId: Long) = intent {
viewModelScope.launch(dispatchers.IO) {
when (val response = removeFollowerUseCase.get()(accountId)) {
is Try.Failure -> postSideEffect(SideEffect.ServerError(response.exception.message))
is Try.Success -> postSideEffect(SideEffect.RefreshSelfAdapter)
}
}
}
private fun follow(accountId: Long) = intent {
viewModelScope.launch(dispatchers.IO) {
when (val response = followUseCase.get()(accountId)) {
is Try.Failure -> postSideEffect(SideEffect.ServerError(response.exception.message))
is Try.Success -> {
//Following Immediately reflects in Ui disable Success message unless asked by QA
// postSideEffect(SideEffect.ServerMessage(response.message))
actionDispatcher.sendAction(InAppAction.FOLLOW_CHANGED)
}
}
}
}
private fun unfollow(accountId: Long) = intent {
viewModelScope.launch(dispatchers.IO) {
when (val response = unfollowUsecase.get()(accountId)) {
is Try.Failure -> postSideEffect(SideEffect.ServerError(response.exception.message))
is Try.Success -> actionDispatcher.sendAction(InAppAction.FOLLOW_CHANGED)
}
}
}
private companion object {
const val ARG_ACCOUNT_ID = "ACCOUNT_ID"
const val ITEMS_PER_PAGE = 30
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment