WIP manejo de errores y reintentos
This commit is contained in:
parent
761a076e52
commit
003ab3ea5d
@ -40,12 +40,7 @@ class AuthRepositoryImpl @Inject constructor(
|
||||
return@flow
|
||||
}
|
||||
|
||||
when(res.code()){
|
||||
401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError))
|
||||
404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError))
|
||||
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||
}
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(res.code())))
|
||||
} catch (_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
|
||||
@ -3,7 +3,26 @@ package com.isolaatti.common
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.isolaatti.utils.Resource
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
|
||||
class ErrorMessageViewModel : ViewModel() {
|
||||
val error: MutableLiveData<Resource.Error.ErrorType> = MutableLiveData()
|
||||
val error: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
|
||||
private val _retry: MutableSharedFlow<Boolean> = MutableSharedFlow()
|
||||
val retry: SharedFlow<Boolean> get() = _retry.asSharedFlow()
|
||||
private var handledCount = 0
|
||||
|
||||
suspend fun askRetry() {
|
||||
_retry.emit(false)
|
||||
handledCount = 0
|
||||
_retry.emit(true)
|
||||
}
|
||||
suspend fun handleRetry() {
|
||||
val subscribers = _retry.subscriptionCount.value
|
||||
|
||||
if(handledCount >= subscribers) {
|
||||
_retry.emit(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -20,6 +20,9 @@ import com.isolaatti.home.HomeActivity
|
||||
import com.isolaatti.login.LogInActivity
|
||||
import com.isolaatti.utils.Resource
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
abstract class IsolaattiBaseActivity : AppCompatActivity() {
|
||||
@ -27,9 +30,10 @@ abstract class IsolaattiBaseActivity : AppCompatActivity() {
|
||||
val errorViewModel: ErrorMessageViewModel by viewModels()
|
||||
|
||||
private var snackbarNetworkStatus: Snackbar? = null
|
||||
private var snackbarError: Snackbar? = null
|
||||
private var connectionHasBeenLost: Boolean = false
|
||||
|
||||
private val errorObserver: Observer<Resource.Error.ErrorType> = Observer {
|
||||
private val errorObserver: Observer<Resource.Error.ErrorType?> = Observer {
|
||||
when(it) {
|
||||
Resource.Error.ErrorType.AuthError -> showReAuthDialog()
|
||||
Resource.Error.ErrorType.NetworkError -> showNetworkErrorMessage()
|
||||
@ -38,6 +42,7 @@ abstract class IsolaattiBaseActivity : AppCompatActivity() {
|
||||
Resource.Error.ErrorType.OtherError -> showUnknownErrorMessage()
|
||||
else -> {}
|
||||
}
|
||||
errorViewModel.error.postValue(null)
|
||||
}
|
||||
|
||||
private val connectivityObserver: Observer<Boolean> = Observer { networkAvailable ->
|
||||
@ -52,21 +57,22 @@ abstract class IsolaattiBaseActivity : AppCompatActivity() {
|
||||
snackbarNetworkStatus = Snackbar.make(view, R.string.network_conn_restored, Snackbar.LENGTH_SHORT)
|
||||
snackbarNetworkStatus?.show()
|
||||
connectionHasBeenLost = false
|
||||
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
errorViewModel.askRetry()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private val signInActivityResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
|
||||
if(activityResult.resultCode == Activity.RESULT_OK) {
|
||||
onRetry()
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
errorViewModel.handleRetry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when a refresh should be performed. For example,
|
||||
* when sign in flow is started completed from here, it is needed to know
|
||||
* when it is complete.
|
||||
*/
|
||||
abstract fun onRetry()
|
||||
|
||||
private val onAcceptReAuthClick = DialogInterface.OnClickListener { _, _ ->
|
||||
signInActivityResult.launch(Intent(this, LogInActivity::class.java))
|
||||
@ -78,22 +84,67 @@ abstract class IsolaattiBaseActivity : AppCompatActivity() {
|
||||
.setPositiveButton(R.string.accept, onAcceptReAuthClick)
|
||||
.setNegativeButton(R.string.close, null)
|
||||
.show()
|
||||
errorViewModel.error.postValue(null)
|
||||
}
|
||||
|
||||
private fun showNetworkErrorMessage() {
|
||||
Toast.makeText(this, R.string.network_error, Toast.LENGTH_SHORT).show()
|
||||
val view: View = window.decorView.findViewById(android.R.id.content) ?: return
|
||||
|
||||
|
||||
snackbarError = Snackbar.make(view, R.string.network_error, Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction(R.string.retry) {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
errorViewModel.askRetry()
|
||||
}
|
||||
errorViewModel.error.postValue(null)
|
||||
}
|
||||
|
||||
snackbarError?.show()
|
||||
}
|
||||
|
||||
private fun showServerErrorMessage() {
|
||||
val view: View = window.decorView.findViewById(android.R.id.content) ?: return
|
||||
|
||||
|
||||
snackbarError = Snackbar.make(view, R.string.server_error, Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction(R.string.retry) {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
errorViewModel.askRetry()
|
||||
}
|
||||
errorViewModel.error.postValue(null)
|
||||
}
|
||||
|
||||
snackbarError?.show()
|
||||
}
|
||||
|
||||
private fun showNotFoundErrorMessage() {
|
||||
val view: View = window.decorView.findViewById(android.R.id.content) ?: return
|
||||
|
||||
|
||||
snackbarError = Snackbar.make(view, R.string.not_found, Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction(R.string.retry) {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
errorViewModel.askRetry()
|
||||
}
|
||||
errorViewModel.error.postValue(null)
|
||||
}
|
||||
|
||||
snackbarError?.show()
|
||||
}
|
||||
|
||||
private fun showUnknownErrorMessage() {
|
||||
val view: View = window.decorView.findViewById(android.R.id.content) ?: return
|
||||
|
||||
|
||||
snackbarError = Snackbar.make(view, R.string.unknown_error, Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction(R.string.retry) {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
errorViewModel.askRetry()
|
||||
}
|
||||
errorViewModel.error.postValue(null)
|
||||
}
|
||||
|
||||
snackbarError?.show()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
@ -2,44 +2,64 @@ package com.isolaatti.followers.data
|
||||
|
||||
import com.isolaatti.followers.data.remote.FollowersApi
|
||||
import com.isolaatti.followers.domain.FollowersRepository
|
||||
import com.isolaatti.profile.data.remote.ProfileListItemDto
|
||||
import com.isolaatti.profile.domain.entity.ProfileListItem
|
||||
import com.isolaatti.utils.IntIdentificationWrapper
|
||||
import com.isolaatti.utils.Resource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import retrofit2.awaitResponse
|
||||
import javax.inject.Inject
|
||||
|
||||
class FollowersRepositoryImpl @Inject constructor(private val followersApi: FollowersApi) : FollowersRepository {
|
||||
override fun getFollowersOfUser(userId: Int, after: Int): Flow<List<ProfileListItem>> = flow {
|
||||
override fun getFollowersOfUser(userId: Int, after: Int): Flow<Resource<List<ProfileListItem>>> = flow {
|
||||
try {
|
||||
val response = followersApi.getFollowersOfUser(userId, after).awaitResponse()
|
||||
if(response.isSuccessful) {
|
||||
response.body()?.let { emit(response.body()!!.map { ProfileListItem.fromDto(it) }) }
|
||||
response.body()?.let { emit(Resource.Success(response.body()!!.map { ProfileListItem.fromDto(it) })) }
|
||||
} else {
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
|
||||
}
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFollowingsOfUser(userId: Int, after: Int): Flow<List<ProfileListItem>> = flow {
|
||||
override fun getFollowingsOfUser(userId: Int, after: Int): Flow<Resource<List<ProfileListItem>>> = flow {
|
||||
try {
|
||||
val response = followersApi.getFollowingsOfUser(userId, after).awaitResponse()
|
||||
if(response.isSuccessful) {
|
||||
response.body()?.let { emit(response.body()!!.map { ProfileListItem.fromDto(it) }) }
|
||||
response.body()?.let { emit(Resource.Success(response.body()!!.map { ProfileListItem.fromDto(it) })) }
|
||||
} else {
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
|
||||
}
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
}
|
||||
|
||||
override fun followUser(userId: Int): Flow<Boolean> = flow {
|
||||
override fun followUser(userId: Int): Flow<Resource<Boolean>> = flow {
|
||||
try {
|
||||
val response = followersApi.followUser(IntIdentificationWrapper(userId)).awaitResponse()
|
||||
if(response.isSuccessful) {
|
||||
emit(true)
|
||||
emit(Resource.Success(true))
|
||||
} else {
|
||||
emit(false)
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
|
||||
}
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
}
|
||||
|
||||
override fun unfollowUser(userId: Int): Flow<Boolean> = flow {
|
||||
override fun unfollowUser(userId: Int): Flow<Resource<Boolean>> = flow {
|
||||
try {
|
||||
val response = followersApi.unfollowUser(IntIdentificationWrapper(userId)).awaitResponse()
|
||||
if(response.isSuccessful) {
|
||||
emit(true)
|
||||
emit(Resource.Success(true))
|
||||
} else {
|
||||
emit(false)
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
|
||||
}
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package com.isolaatti.followers.domain
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class FollowUserUseCase @Inject constructor(private val followersRepository: FollowersRepository) {
|
||||
fun follow(userId: Int): Flow<Boolean> {
|
||||
return followersRepository.followUser(userId)
|
||||
}
|
||||
|
||||
fun unfollow(userId: Int): Flow<Boolean> {
|
||||
return followersRepository.unfollowUser(userId)
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,12 @@
|
||||
package com.isolaatti.followers.domain
|
||||
|
||||
import com.isolaatti.profile.domain.entity.ProfileListItem
|
||||
import com.isolaatti.utils.Resource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface FollowersRepository {
|
||||
fun getFollowersOfUser(userId: Int, after: Int): Flow<List<ProfileListItem>>
|
||||
fun getFollowingsOfUser(userId: Int, after: Int): Flow<List<ProfileListItem>>
|
||||
fun followUser(userId: Int): Flow<Boolean>
|
||||
fun unfollowUser(userId: Int): Flow<Boolean>
|
||||
fun getFollowersOfUser(userId: Int, after: Int): Flow<Resource<List<ProfileListItem>>>
|
||||
fun getFollowingsOfUser(userId: Int, after: Int): Flow<Resource<List<ProfileListItem>>>
|
||||
fun followUser(userId: Int): Flow<Resource<Boolean>>
|
||||
fun unfollowUser(userId: Int): Flow<Resource<Boolean>>
|
||||
}
|
||||
@ -4,9 +4,9 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.isolaatti.followers.domain.FollowUserUseCase
|
||||
import com.isolaatti.followers.domain.FollowersRepository
|
||||
import com.isolaatti.profile.domain.entity.ProfileListItem
|
||||
import com.isolaatti.utils.Resource
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
@ -16,7 +16,7 @@ import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class FollowersViewModel @Inject constructor(private val followersRepository: FollowersRepository, private val followUserUseCase: FollowUserUseCase) : ViewModel() {
|
||||
class FollowersViewModel @Inject constructor(private val followersRepository: FollowersRepository) : ViewModel() {
|
||||
var userId: Int = 0
|
||||
|
||||
private val followersList: MutableList<ProfileListItem> = mutableListOf()
|
||||
@ -29,6 +29,19 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo
|
||||
val followers: LiveData<List<ProfileListItem>> get() = _followers
|
||||
val followings: LiveData<List<ProfileListItem>> get() = _followings
|
||||
|
||||
private val toRetry: MutableList<Runnable> = mutableListOf()
|
||||
|
||||
|
||||
// runs the lists of "Runnable" one by one and clears list. After this is executed,
|
||||
// caller should report as handled
|
||||
fun retry() {
|
||||
toRetry.forEach {
|
||||
it.run()
|
||||
}
|
||||
|
||||
toRetry.clear()
|
||||
}
|
||||
|
||||
|
||||
private fun getFollowersLastId(): Int {
|
||||
if(followersList.isEmpty()) {
|
||||
@ -53,11 +66,23 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo
|
||||
|
||||
viewModelScope.launch {
|
||||
followersRepository.getFollowersOfUser(userId, getFollowersLastId()).onEach {
|
||||
followersList.addAll(it)
|
||||
_followers.postValue(followersList)
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
when(it) {
|
||||
is Resource.Success -> {
|
||||
if(it.data != null) {
|
||||
followersList.addAll(it.data)
|
||||
}
|
||||
|
||||
_followers.postValue(followersList)
|
||||
}
|
||||
is Resource.Error -> {
|
||||
toRetry.add {
|
||||
fetchFollowers()
|
||||
}
|
||||
}
|
||||
is Resource.Loading -> {}
|
||||
}
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchFollowings() {
|
||||
@ -67,8 +92,20 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo
|
||||
|
||||
viewModelScope.launch {
|
||||
followersRepository.getFollowingsOfUser(userId, getFollowingsLastId()).onEach {
|
||||
followingsList.addAll(it)
|
||||
when(it) {
|
||||
is Resource.Error -> {
|
||||
toRetry.add {
|
||||
fetchFollowings()
|
||||
}
|
||||
}
|
||||
is Resource.Loading -> {}
|
||||
is Resource.Success -> {
|
||||
if(it.data != null) {
|
||||
followingsList.addAll(it.data)
|
||||
_followings.postValue(followingsList)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
}
|
||||
@ -96,10 +133,20 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo
|
||||
replaceOnLists(user)
|
||||
|
||||
viewModelScope.launch {
|
||||
followUserUseCase.follow(user.id).onEach {
|
||||
followersRepository.followUser(user.id).onEach {
|
||||
when(it) {
|
||||
is Resource.Error -> {
|
||||
toRetry.add {
|
||||
followUser(user)
|
||||
}
|
||||
}
|
||||
is Resource.Loading -> {}
|
||||
is Resource.Success -> {
|
||||
user.following = true
|
||||
user.updatingFollowState = false
|
||||
replaceOnLists(user)
|
||||
}
|
||||
}
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
}
|
||||
@ -110,10 +157,21 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo
|
||||
replaceOnLists(user)
|
||||
|
||||
viewModelScope.launch {
|
||||
followUserUseCase.unfollow(user.id).onEach {
|
||||
followersRepository.unfollowUser(user.id).onEach {
|
||||
when(it) {
|
||||
is Resource.Error -> {
|
||||
toRetry.add {
|
||||
unfollowUser(user)
|
||||
}
|
||||
}
|
||||
is Resource.Loading -> {}
|
||||
is Resource.Success -> {
|
||||
user.following = false
|
||||
user.updatingFollowState = false
|
||||
replaceOnLists(user)
|
||||
}
|
||||
}
|
||||
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,10 @@ import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.isolaatti.BuildConfig
|
||||
import com.isolaatti.R
|
||||
@ -45,6 +48,7 @@ import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.MarkwonConfiguration
|
||||
import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute
|
||||
import io.noties.markwon.linkify.LinkifyPlugin
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
||||
@ -57,7 +61,6 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
||||
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
|
||||
private val viewModel: FeedViewModel by activityViewModels()
|
||||
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
|
||||
|
||||
private var currentUserId = 0
|
||||
|
||||
private lateinit var viewBinding: FragmentFeedBinding
|
||||
@ -220,9 +223,19 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
||||
viewModel.errorLoading.observe(viewLifecycleOwner) {
|
||||
errorViewModel.error.postValue(it)
|
||||
}
|
||||
|
||||
viewModel.getProfile()
|
||||
|
||||
optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
errorViewModel.retry.collect {
|
||||
viewModel.retry()
|
||||
errorViewModel.handleRetry()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLiked(postId: Long) = viewModel.likePost(postId)
|
||||
|
||||
@ -15,10 +15,6 @@ import com.isolaatti.home.presentation.FeedViewModel
|
||||
class HomeActivity : IsolaattiBaseActivity() {
|
||||
private lateinit var viewBinding: ActivityHomeBinding
|
||||
private val feedViewModel: FeedViewModel by viewModels()
|
||||
override fun onRetry() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@ -25,6 +25,19 @@ class FeedViewModel @Inject constructor(
|
||||
private val postsRepository: PostsRepository
|
||||
) : PostListingViewModelBase() {
|
||||
|
||||
private val toRetry: MutableList<Runnable> = mutableListOf()
|
||||
|
||||
|
||||
// runs the lists of "Runnable" one by one and clears list. After this is executed,
|
||||
// caller should report as handled
|
||||
fun retry() {
|
||||
toRetry.forEach {
|
||||
it.run()
|
||||
}
|
||||
|
||||
toRetry.clear()
|
||||
}
|
||||
|
||||
override fun getFeed(refresh: Boolean) {
|
||||
viewModelScope.launch {
|
||||
if (refresh) {
|
||||
@ -49,7 +62,10 @@ class FeedViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
//errorLoading.postValue(feedDtoResource.errorType)
|
||||
errorLoading.postValue(listResource.errorType)
|
||||
toRetry.add {
|
||||
getFeed(refresh)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -67,15 +83,24 @@ class FeedViewModel @Inject constructor(
|
||||
authRepository.getUserId().onEach { userId ->
|
||||
userId?.let {
|
||||
getProfileUseCase(userId).onEach { profile ->
|
||||
if (profile is Resource.Success) {
|
||||
|
||||
|
||||
when(profile) {
|
||||
is Resource.Error -> {
|
||||
errorLoading.postValue(profile.errorType)
|
||||
toRetry.add {
|
||||
getProfile()
|
||||
}
|
||||
}
|
||||
is Resource.Loading -> {}
|
||||
is Resource.Success -> {
|
||||
profile.data?.let { _userProfile.postValue(it) }
|
||||
}
|
||||
}
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,9 +21,6 @@ class PostViewerActivity : IsolaattiBaseActivity() {
|
||||
private lateinit var binding: ActivityPostViewerBinding
|
||||
|
||||
private var postId: Long? = null
|
||||
override fun onRetry() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@ -14,21 +14,36 @@ import javax.inject.Inject
|
||||
|
||||
class CommentsRepositoryImpl @Inject constructor(private val commentsApi: CommentsApi) :
|
||||
CommentsRepository {
|
||||
override fun getComments(postId: Long, lastId: Long): Flow<MutableList<Comment>> = flow {
|
||||
override fun getComments(postId: Long, lastId: Long): Flow<Resource<MutableList<Comment>>> = flow {
|
||||
try {
|
||||
emit(Resource.Loading())
|
||||
val response = commentsApi.getCommentsOfPosts(postId, lastId, 15).awaitResponse()
|
||||
if(response.isSuccessful){
|
||||
response.body()?.let { emit(Comment.fromCommentsDto(it).toMutableList()) }
|
||||
response.body()?.let { emit(Resource.Success(Comment.fromCommentsDto(it).toMutableList())) }
|
||||
} else {
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
|
||||
}
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getComment(commentId: Long): Flow<CommentDto> = flow {
|
||||
override fun getComment(commentId: Long): Flow<Resource<CommentDto>> = flow {
|
||||
try {
|
||||
emit(Resource.Loading())
|
||||
val response = commentsApi.getComment(commentId).awaitResponse()
|
||||
if(response.isSuccessful) {
|
||||
response.body()?.let { emit(it) }
|
||||
response.body()?.let { emit(Resource.Success(it)) }
|
||||
} else {
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
|
||||
}
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
}
|
||||
|
||||
override fun postComment(content: String, audioId: String?, postId: Long): Flow<Resource<Comment>> = flow {
|
||||
try {
|
||||
emit(Resource.Loading())
|
||||
val commentToPostDto = CommentToPostDto(content, audioId)
|
||||
val response = commentsApi.postComment(postId, commentToPostDto).awaitResponse()
|
||||
@ -38,11 +53,17 @@ class CommentsRepositoryImpl @Inject constructor(private val commentsApi: Commen
|
||||
emit(Resource.Success(Comment.fromCommentDto(responseBody)))
|
||||
return@flow
|
||||
}
|
||||
} else {
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
|
||||
}
|
||||
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
emit(Resource.Error())
|
||||
}
|
||||
|
||||
override fun editComment(commentId: Long, content: String, audioId: String?): Flow<Resource<Comment>> = flow {
|
||||
try {
|
||||
emit(Resource.Loading())
|
||||
val commentToPostDto = CommentToPostDto(content, audioId)
|
||||
val response = commentsApi.editComment(commentId, commentToPostDto).awaitResponse()
|
||||
@ -53,8 +74,12 @@ class CommentsRepositoryImpl @Inject constructor(private val commentsApi: Commen
|
||||
emit(Resource.Success(Comment.fromCommentDto(responseBody)))
|
||||
return@flow
|
||||
}
|
||||
} else {
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
|
||||
}
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
emit(Resource.Error())
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -6,8 +6,8 @@ import com.isolaatti.utils.Resource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface CommentsRepository {
|
||||
fun getComments(postId: Long, lastId: Long): Flow<MutableList<Comment>>
|
||||
fun getComment(commentId: Long): Flow<CommentDto>
|
||||
fun getComments(postId: Long, lastId: Long): Flow<Resource<MutableList<Comment>>>
|
||||
fun getComment(commentId: Long): Flow<Resource<CommentDto>>
|
||||
fun postComment(content: String, audioId: String?, postId: Long): Flow<Resource<Comment>>
|
||||
fun editComment(commentId: Long, content: String, audioId: String?): Flow<Resource<Comment>>
|
||||
fun deleteComment(commentId: Long): Flow<Resource<Boolean>>
|
||||
|
||||
@ -2,10 +2,11 @@ package com.isolaatti.posting.comments.domain.use_case
|
||||
|
||||
import com.isolaatti.posting.comments.domain.CommentsRepository
|
||||
import com.isolaatti.posting.comments.domain.model.Comment
|
||||
import com.isolaatti.utils.Resource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetComments @Inject constructor(private val commentsRepository: CommentsRepository) {
|
||||
operator fun invoke(postId: Long, lastId: Long? = null): Flow<MutableList<Comment>> =
|
||||
operator fun invoke(postId: Long, lastId: Long? = null): Flow<Resource<MutableList<Comment>>> =
|
||||
commentsRepository.getComments(postId, lastId ?: 0)
|
||||
}
|
||||
@ -34,7 +34,20 @@ class CommentsViewModel @Inject constructor(
|
||||
val noMoreContent: MutableLiveData<Boolean?> = MutableLiveData()
|
||||
val commentToEdit: MutableLiveData<Comment?> = MutableLiveData()
|
||||
val finishedEditingComment: MutableLiveData<Boolean?> = MutableLiveData()
|
||||
val error: MutableLiveData<Boolean> = MutableLiveData(false)
|
||||
val error: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
|
||||
|
||||
private val toRetry: MutableList<Runnable> = mutableListOf()
|
||||
|
||||
|
||||
// runs the lists of "Runnable" one by one and clears list. After this is executed,
|
||||
// caller should report as handled
|
||||
fun retry() {
|
||||
toRetry.forEach {
|
||||
it.run()
|
||||
}
|
||||
|
||||
toRetry.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* postId to query comments for. First page will be fetched when set.
|
||||
@ -54,17 +67,30 @@ class CommentsViewModel @Inject constructor(
|
||||
commentsList.clear()
|
||||
}
|
||||
getComments(postId, lastId).onEach {
|
||||
when(it) {
|
||||
is Resource.Error -> {
|
||||
error.postValue(it.errorType)
|
||||
toRetry.add {
|
||||
getContent(refresh)
|
||||
}
|
||||
}
|
||||
is Resource.Loading -> {}
|
||||
is Resource.Success -> {
|
||||
val eventType =
|
||||
if ((commentsList.isNotEmpty())) UpdateEvent.UpdateType.COMMENT_PAGE_ADDED_BOTTOM else UpdateEvent.UpdateType.REFRESH
|
||||
commentsList.addAll(it)
|
||||
if(it.data == null) {
|
||||
return@onEach
|
||||
}
|
||||
commentsList.addAll(it.data)
|
||||
_comments.postValue(Pair(commentsList, UpdateEvent(eventType, null)))
|
||||
if (it.isEmpty()) {
|
||||
if (it.data.isEmpty()) {
|
||||
noMoreContent.postValue(true)
|
||||
}
|
||||
if (it.isNotEmpty()) {
|
||||
lastId = it.last().id
|
||||
if (it.data.isNotEmpty()) {
|
||||
lastId = it.data.last().id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
}
|
||||
@ -84,7 +110,13 @@ class CommentsViewModel @Inject constructor(
|
||||
|
||||
is Resource.Error -> {
|
||||
commentPosted.postValue(false)
|
||||
error.postValue(true)
|
||||
error.postValue(it.errorType)
|
||||
|
||||
|
||||
// this is the original call, put to retry
|
||||
toRetry.add {
|
||||
postComment(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
@ -111,7 +143,11 @@ class CommentsViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
error.postValue(true)
|
||||
error.postValue(commentResource.errorType)
|
||||
|
||||
toRetry.add {
|
||||
editComment(newContent)
|
||||
}
|
||||
}
|
||||
|
||||
is Resource.Loading -> {}
|
||||
@ -129,7 +165,11 @@ class CommentsViewModel @Inject constructor(
|
||||
deleteCommentUseCase(commentId).onEach {
|
||||
when(it) {
|
||||
is Resource.Error -> {
|
||||
error.postValue(true)
|
||||
error.postValue(it.errorType)
|
||||
|
||||
toRetry.add {
|
||||
deleteComment(commentId)
|
||||
}
|
||||
}
|
||||
is Resource.Loading -> {}
|
||||
is Resource.Success -> {
|
||||
|
||||
@ -10,7 +10,10 @@ import androidx.core.view.doOnLayout
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
|
||||
@ -19,6 +22,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.common.Dialogs
|
||||
import com.isolaatti.common.ErrorMessageViewModel
|
||||
import com.isolaatti.databinding.BottomSheetPostCommentsBinding
|
||||
import com.isolaatti.posting.comments.domain.model.Comment
|
||||
import com.isolaatti.posting.comments.presentation.CommentsRecyclerViewAdapter
|
||||
@ -38,6 +42,8 @@ import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.MarkwonConfiguration
|
||||
import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute
|
||||
import io.noties.markwon.linkify.LinkifyPlugin
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedCallback {
|
||||
@ -45,8 +51,8 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
|
||||
private lateinit var viewBinding: BottomSheetPostCommentsBinding
|
||||
val viewModel: CommentsViewModel by viewModels()
|
||||
private lateinit var adapter: CommentsRecyclerViewAdapter
|
||||
|
||||
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
|
||||
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
|
||||
private val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
|
||||
|
||||
private val optionsObserver: Observer<OptionClicked?> = Observer { optionClicked ->
|
||||
if(optionClicked?.callerId == CALLER_ID) {
|
||||
@ -117,10 +123,7 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
|
||||
viewModel.commentPosted.observe(viewLifecycleOwner, commentPostedObserver)
|
||||
viewModel.commentToEdit.observe(viewLifecycleOwner, commentToEditObserver)
|
||||
viewModel.error.observe(viewLifecycleOwner) {
|
||||
if(it == true) {
|
||||
Toast.makeText(requireContext(), "error", Toast.LENGTH_SHORT).show()
|
||||
viewModel.error.postValue(null)
|
||||
}
|
||||
errorViewModel.error.postValue(it)
|
||||
}
|
||||
|
||||
optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver)
|
||||
@ -200,6 +203,18 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
|
||||
// even if no change event is triggered
|
||||
viewBinding.submitCommentButton.isEnabled = !viewBinding.newCommentTextField.editText?.text.isNullOrBlank()
|
||||
|
||||
|
||||
|
||||
// things to retry when user taps "Retry"
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
errorViewModel.retry.collect {
|
||||
viewModel.retry()
|
||||
errorViewModel.handleRetry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setObservers()
|
||||
setListeners()
|
||||
}
|
||||
|
||||
@ -83,10 +83,7 @@ class EditCommentDialogFragment : DialogFragment() {
|
||||
}
|
||||
|
||||
viewModel.error.observe(viewLifecycleOwner) {
|
||||
if(it) {
|
||||
Toast.makeText(requireContext(), "error", Toast.LENGTH_SHORT).show()
|
||||
viewModel.error.postValue(true)
|
||||
}
|
||||
viewModel.error.postValue(it)
|
||||
}
|
||||
|
||||
viewModel.finishedEditingComment.observe(viewLifecycleOwner) {
|
||||
|
||||
@ -14,6 +14,7 @@ import com.isolaatti.posting.posts.domain.entity.Post
|
||||
import com.isolaatti.utils.Resource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import retrofit2.awaitResponse
|
||||
import javax.inject.Inject
|
||||
|
||||
class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, private val postApi: PostApi) : PostsRepository {
|
||||
@ -40,17 +41,12 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr
|
||||
emit(Resource.Loading())
|
||||
try {
|
||||
val gson = Gson()
|
||||
val result = feedsApi.postsOfUser(userId, 20, lastId, olderFirst, gson.toJson(filter)).execute()
|
||||
if(result.isSuccessful) {
|
||||
emit(Resource.Success(result.body()?.let { Post.fromFeedDto(it) }))
|
||||
val response = feedsApi.postsOfUser(userId, 20, lastId, olderFirst, gson.toJson(filter)).awaitResponse()
|
||||
if(response.isSuccessful) {
|
||||
emit(Resource.Success(response.body()?.let { Post.fromFeedDto(it) }))
|
||||
return@flow
|
||||
}
|
||||
when(result.code()) {
|
||||
401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError))
|
||||
404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError))
|
||||
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||
}
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
@ -59,17 +55,12 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr
|
||||
override fun makePost(createPostDto: CreatePostDto): Flow<Resource<FeedDto.PostDto>> = flow {
|
||||
emit(Resource.Loading())
|
||||
try {
|
||||
val result = postApi.makePost(createPostDto).execute()
|
||||
val result = postApi.makePost(createPostDto).awaitResponse()
|
||||
if(result.isSuccessful) {
|
||||
emit(Resource.Success(result.body()))
|
||||
return@flow
|
||||
}
|
||||
when(result.code()) {
|
||||
401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError))
|
||||
404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError))
|
||||
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||
}
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(result.code())))
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
@ -78,17 +69,12 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr
|
||||
override fun editPost(editPostDto: EditPostDto): Flow<Resource<FeedDto.PostDto>> = flow {
|
||||
emit(Resource.Loading())
|
||||
try {
|
||||
val result = postApi.editPost(editPostDto).execute()
|
||||
val result = postApi.editPost(editPostDto).awaitResponse()
|
||||
if(result.isSuccessful) {
|
||||
emit(Resource.Success(result.body()))
|
||||
return@flow
|
||||
}
|
||||
when(result.code()) {
|
||||
401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError))
|
||||
404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError))
|
||||
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||
}
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(result.code())))
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
@ -102,12 +88,7 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr
|
||||
emit(Resource.Success(result.body()))
|
||||
return@flow
|
||||
}
|
||||
when(result.code()) {
|
||||
401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError))
|
||||
404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError))
|
||||
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||
}
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(result.code())))
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
@ -121,12 +102,7 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr
|
||||
emit(Resource.Success(result.body()))
|
||||
return@flow
|
||||
}
|
||||
when(result.code()) {
|
||||
401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError))
|
||||
404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError))
|
||||
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||
}
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(result.code())))
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
|
||||
@ -6,11 +6,15 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.common.IsolaattiBaseActivity
|
||||
import com.isolaatti.databinding.ActivityCreatePostBinding
|
||||
import com.isolaatti.posting.posts.presentation.CreatePostViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class CreatePostActivity : IsolaattiBaseActivity() {
|
||||
@ -34,13 +38,6 @@ class CreatePostActivity : IsolaattiBaseActivity() {
|
||||
val viewModel: CreatePostViewModel by viewModels()
|
||||
var mode: Int = EXTRA_MODE_CREATE
|
||||
var postId: Long = 0L
|
||||
override fun onRetry() {
|
||||
if(mode == EXTRA_MODE_EDIT && postId != 0L) {
|
||||
viewModel.editDiscussion(postId, binding.filledTextField.editText?.text.toString())
|
||||
} else {
|
||||
viewModel.postDiscussion(binding.filledTextField.editText?.text.toString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -62,9 +59,24 @@ class CreatePostActivity : IsolaattiBaseActivity() {
|
||||
setupUI()
|
||||
setListeners()
|
||||
setObservers()
|
||||
|
||||
setContentView(binding.root)
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
errorViewModel.retry.collect {
|
||||
if(!it) {
|
||||
return@collect
|
||||
}
|
||||
|
||||
if(mode == EXTRA_MODE_EDIT && postId != 0L) {
|
||||
viewModel.editDiscussion(postId, binding.filledTextField.editText?.text.toString())
|
||||
} else {
|
||||
viewModel.postDiscussion(binding.filledTextField.editText?.text.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun setupUI() {
|
||||
|
||||
@ -6,24 +6,18 @@ import com.isolaatti.profile.domain.ProfileRepository
|
||||
import com.isolaatti.utils.Resource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import retrofit2.await
|
||||
import retrofit2.awaitResponse
|
||||
import javax.inject.Inject
|
||||
|
||||
class ProfileRepositoryImpl @Inject constructor(private val profileApi: ProfileApi) : ProfileRepository {
|
||||
override fun getProfile(userId: Int): Flow<Resource<UserProfileDto>> = flow {
|
||||
try {
|
||||
val result = profileApi.userProfile(userId).execute()
|
||||
val result = profileApi.userProfile(userId).awaitResponse()
|
||||
if(result.isSuccessful) {
|
||||
emit(Resource.Success(result.body()))
|
||||
return@flow
|
||||
}
|
||||
|
||||
when(result.code()) {
|
||||
401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError))
|
||||
404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError))
|
||||
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||
}
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(result.code())))
|
||||
} catch(_: Exception) {
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
|
||||
@ -35,10 +35,31 @@ class ProfileViewModel @Inject constructor(private val getProfileUseCase: GetPro
|
||||
|
||||
val followingState: MutableLiveData<FollowingState> = MutableLiveData()
|
||||
|
||||
private val toRetry: MutableList<Runnable> = mutableListOf()
|
||||
|
||||
|
||||
// runs the lists of "Runnable" one by one and clears list. After this is executed,
|
||||
// caller should report as handled
|
||||
fun retry() {
|
||||
toRetry.forEach {
|
||||
it.run()
|
||||
}
|
||||
|
||||
toRetry.clear()
|
||||
}
|
||||
|
||||
fun getProfile() {
|
||||
viewModelScope.launch {
|
||||
getProfileUseCase(profileId).onEach {
|
||||
if(it is Resource.Success) {
|
||||
when(it) {
|
||||
is Resource.Error -> {
|
||||
errorLoading.postValue(it.errorType)
|
||||
toRetry.add {
|
||||
getProfile()
|
||||
}
|
||||
}
|
||||
is Resource.Loading -> {}
|
||||
is Resource.Success -> {
|
||||
_profile.postValue(it.data!!)
|
||||
followingState.postValue(
|
||||
it.data.let {user->
|
||||
@ -51,6 +72,7 @@ class ProfileViewModel @Inject constructor(private val getProfileUseCase: GetPro
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
}
|
||||
@ -75,6 +97,9 @@ class ProfileViewModel @Inject constructor(private val getProfileUseCase: GetPro
|
||||
|
||||
is Resource.Error -> {
|
||||
errorLoading.postValue(feedDtoResource.errorType)
|
||||
toRetry.add {
|
||||
getFeed(refresh)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
|
||||
@ -24,6 +24,7 @@ import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.isolaatti.BuildConfig
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.common.IsolaattiBaseActivity
|
||||
import com.isolaatti.databinding.ActivityProfileBinding
|
||||
import com.isolaatti.posting.common.domain.OnUserInteractedCallback
|
||||
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
|
||||
@ -42,7 +43,7 @@ import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAb
|
||||
import io.noties.markwon.linkify.LinkifyPlugin
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ProfileActivity : FragmentActivity() {
|
||||
class ProfileActivity : IsolaattiBaseActivity() {
|
||||
|
||||
lateinit var viewBinding: ActivityProfileBinding
|
||||
|
||||
|
||||
@ -9,7 +9,10 @@ import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.isolaatti.BuildConfig
|
||||
@ -43,6 +46,7 @@ import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.MarkwonConfiguration
|
||||
import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute
|
||||
import io.noties.markwon.linkify.LinkifyPlugin
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ProfileMainFragment : Fragment() {
|
||||
@ -306,5 +310,16 @@ class ProfileMainFragment : Fragment() {
|
||||
bind()
|
||||
setObservers()
|
||||
getData()
|
||||
|
||||
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
errorViewModel.retry.collect {
|
||||
viewModel.retry()
|
||||
errorViewModel.handleRetry()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,5 +7,15 @@ sealed class Resource<T> {
|
||||
enum class ErrorType {
|
||||
NetworkError, AuthError, NotFoundError, ServerError, OtherError
|
||||
}
|
||||
companion object {
|
||||
fun mapErrorCode(errorCode: Int): ErrorType {
|
||||
return when(errorCode) {
|
||||
401 -> ErrorType.AuthError
|
||||
404 -> ErrorType.NotFoundError
|
||||
505 -> ErrorType.ServerError
|
||||
else -> ErrorType.OtherError
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,4 +77,7 @@
|
||||
<string name="edit_comment">Edit comment</string>
|
||||
<string name="network_conn_lost">Network connection lost</string>
|
||||
<string name="network_conn_restored">Network connection restored</string>
|
||||
<string name="retry">Retry</string>
|
||||
<string name="not_found">The resource you are trying to load could not be found</string>
|
||||
<string name="unknown_error">An unkwnow error occurred</string>
|
||||
</resources>
|
||||
Loading…
x
Reference in New Issue
Block a user