Last active
September 6, 2023 06:40
-
-
Save Aldikitta/1daece80397b70acbb682b692d6037ec to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//RESOURCE CLASS | |
package com.kreditplus.sally.core.utils.network | |
import com.google.gson.Gson | |
import com.google.gson.JsonParser | |
import com.google.gson.annotations.SerializedName | |
import com.kreditplus.sally.core.base.dto.KreditPlusBaseResponseDto | |
import kotlinx.coroutines.flow.Flow | |
import kotlinx.coroutines.flow.catch | |
import kotlinx.coroutines.flow.map | |
import kotlinx.coroutines.flow.onCompletion | |
import kotlinx.coroutines.flow.onStart | |
import org.json.JSONException | |
import org.json.JSONObject | |
import retrofit2.HttpException | |
import java.io.IOException | |
const val CLIENT_ERROR = "Terjadi kesalahan, mohon periksa masukan anda" | |
const val SERVER_ERROR = "Terjadi kesalahan pada Server, coba lagi nanti" | |
const val NETWORK_ERROR = "Koneksi internet bermasalah, coba lagi nanti" | |
const val HTTP_UNKNOWN_ERROR = "HTTP Error tidak diketahui (exc: 4xx/5xx)" | |
const val UNKNOWN_ERROR = "Error tidak diketahui" | |
sealed interface KreditPlusResponse<out T> { | |
data class Success<T>(val data: T) : KreditPlusResponse<T> | |
data class Error<T>( | |
val exception: AppException, | |
val errorCode: String? = null, | |
val responseBodyError: String? = null, | |
val error: ErrorResponse? = null | |
) : | |
KreditPlusResponse<T> | |
data class Loading(val status: Boolean) : KreditPlusResponse<Nothing> | |
} | |
open class AppException( | |
message: String? = null, | |
cause: Throwable? = null, | |
val responseBodyError: String? = null, | |
) : Throwable(message, cause) | |
class NetworkException(message: String? = null, cause: Throwable? = null) : | |
AppException(message, cause) | |
class ServerException( | |
message: String? = null, | |
cause: Throwable? = null, | |
responseBodyError: String? = null | |
) : | |
AppException(message, cause, responseBodyError) | |
class ClientException( | |
message: String? = null, | |
cause: Throwable? = null, | |
responseBodyError: String? = null | |
) : | |
AppException(message, cause, responseBodyError) | |
class UnknownException( | |
message: String? = null, | |
cause: Throwable? = null, | |
responseBodyError: String? = null | |
) : | |
AppException(message, cause, responseBodyError) | |
suspend fun <T> asSuspendKreditPlusResponse(apiCall: suspend () -> T, loadingState: (Boolean) -> Unit): KreditPlusResponse<T> { | |
return try { | |
loadingState(true) | |
val response = apiCall.invoke() | |
KreditPlusResponse.Success(response) | |
} catch (error: Throwable) { | |
val exception = when (error) { | |
is HttpException -> { | |
val errorBody = error.response()?.errorBody()?.string() | |
when (error.code()) { | |
in 400..499 -> { | |
ClientException( | |
message = "${CLIENT_ERROR}: ${error.code()}", | |
cause = error, | |
responseBodyError = errorBody | |
) | |
} | |
in 500..599 -> ServerException( | |
message = "${SERVER_ERROR}: ${error.code()}", | |
cause = error, | |
responseBodyError = errorBody | |
) | |
else -> UnknownException( | |
message = "${HTTP_UNKNOWN_ERROR}: ${error.code()}", | |
cause = error, | |
responseBodyError = errorBody | |
) | |
} | |
} | |
is IOException -> NetworkException( | |
message = NETWORK_ERROR, | |
cause = error | |
) | |
else -> AppException( | |
message = UNKNOWN_ERROR, | |
cause = error | |
) | |
} | |
val errorCode = when (error) { | |
is HttpException -> { | |
when (error.code()) { | |
in 400..499 -> { | |
"#ER${error.code()}" | |
} | |
in 500..599 -> { | |
"#ER${error.code()}" | |
} | |
else -> { | |
"#ER${error.code()}" | |
} | |
} | |
} | |
else -> { | |
"Outside: 4xx/5xx" | |
} | |
} | |
val errorResponse = Gson().fromJson(exception.responseBodyError, ErrorResponse::class.java) | |
KreditPlusResponse.Error(exception, errorCode, exception.responseBodyError, errorResponse) | |
} finally { | |
loadingState(false) | |
} | |
} | |
fun <T> Flow<T>.asFlowKreditPlusResponse(): Flow<KreditPlusResponse<T>> { | |
return this | |
.map<T, KreditPlusResponse<T>> { | |
KreditPlusResponse.Success(it) | |
} | |
.onStart { emit(KreditPlusResponse.Loading(true)) } | |
.onCompletion { emit(KreditPlusResponse.Loading(false)) } | |
.catch { error -> | |
val exception = when (error) { | |
is HttpException -> { | |
val errorBody = error.response()?.errorBody()?.string() | |
when (error.code()) { | |
in 400..499 -> { | |
ClientException( | |
message = "${CLIENT_ERROR}: ${error.code()}", | |
cause = error, | |
responseBodyError = errorBody | |
) | |
} | |
in 500..599 -> { | |
ServerException( | |
message = "${SERVER_ERROR}: ${error.code()}", | |
cause = error, | |
responseBodyError = errorBody | |
) | |
} | |
else -> { | |
UnknownException( | |
message = "${HTTP_UNKNOWN_ERROR}: ${error.code()}", | |
cause = error, | |
responseBodyError = errorBody | |
) | |
} | |
} | |
} | |
is IOException -> NetworkException( | |
message = NETWORK_ERROR, | |
cause = error | |
) | |
else -> AppException( | |
message = UNKNOWN_ERROR, | |
cause = error | |
) | |
} | |
val errorCode = when (error) { | |
is HttpException -> { | |
when (error.code()) { | |
in 400..499 -> { | |
"#ER${error.code()}" | |
} | |
in 500..599 -> { | |
"#ER${error.code()}" | |
} | |
else -> { | |
"#ER${error.code()}" | |
} | |
} | |
} | |
else -> { | |
"Outside: 4xx/5xx" | |
} | |
} | |
val errorResponse = Gson().fromJson(exception.responseBodyError, ErrorResponse::class.java) | |
emit(KreditPlusResponse.Error(exception, errorCode, exception.responseBodyError, errorResponse)) | |
} | |
} | |
// This code is use to handle exception error in paging | |
fun asPagingKreditPlusResponse(cause: Throwable?): LoadStateError { | |
val error = LoadStateError() | |
when (cause) { | |
is IOException -> { | |
error.errorMessage = cause.message | |
} | |
is HttpException -> { | |
error.errorCode = cause.code() | |
error.errorMessage = cause.message | |
error.responseBodyError = cause.response()?.errorBody()?.string() | |
error.errorMessageStatic = "${HTTP_UNKNOWN_ERROR}: ${cause.code()}" | |
when (error.errorCode) { | |
in 400..499 -> { | |
error.errorMessageStatic = "${CLIENT_ERROR}: ${cause.code()}" | |
} | |
in 500..599 -> { | |
error.errorMessageStatic = "${SERVER_ERROR}: ${cause.code()}" | |
} | |
else -> { | |
error.errorMessageStatic = "${HTTP_UNKNOWN_ERROR}: ${cause.code()}" | |
} | |
} | |
} | |
else -> { | |
error.errorMessage = cause?.message | |
} | |
} | |
return error | |
} | |
data class LoadStateError( | |
var errorCode: Int? = null, | |
var errorMessage: String? = null, | |
var responseBodyError: String? = null, | |
var errorMessageStatic: String? = null | |
) | |
fun asResponseErrorResponse( | |
responseBodyError: String?, | |
statusFieldName: String = "status", | |
validationArrayName: String = "validation", | |
fieldFieldName: String = "field", | |
messageFieldName: String = "message", | |
statusType: String = "Error validation" | |
): Map<String, String> { | |
val errorMap = mutableMapOf<String, String>() | |
responseBodyError?.let { | |
try { | |
val json = JSONObject(responseBodyError) | |
if (json.has(statusFieldName) && json.getString(statusFieldName) == statusType) { | |
val validationArray = json.getJSONArray(validationArrayName) | |
for (i in 0 until validationArray.length()) { | |
val validationObj = validationArray.getJSONObject(i) | |
val field = validationObj.getString(fieldFieldName) | |
val message = validationObj.getString(messageFieldName) | |
errorMap[field] = message | |
} | |
} else { | |
val validationArray = json.getJSONArray(validationArrayName) | |
for (i in 0 until validationArray.length()) { | |
val validationObj = validationArray.getJSONObject(i) | |
val field = validationObj.getString(fieldFieldName) | |
val message = validationObj.getString(messageFieldName) | |
errorMap[field] = message | |
} | |
} | |
} catch (e: JSONException) { | |
// Handle JSON parsing error if needed | |
} | |
} | |
return errorMap | |
} | |
data class ErrorResponse( | |
@SerializedName("code") | |
val code: Int?, | |
@SerializedName("data") | |
val data: Any?, | |
@SerializedName("message") | |
val message: String?, | |
@SerializedName("request_id") | |
val requestId: String?, | |
@SerializedName("timestamp") | |
val timestamp: String? | |
) | |
// SUSPEND FUNCTION | |
// API SERVICE | |
@GET("agent-visit/{id}") | |
suspend fun getDetailAgentVisitWithFullResponse( | |
@Header("Authorization") token: String, | |
@Path(value = "id", encoded = true) id: Int | |
): BaseResponse<GetAgentDetailResponse> | |
// DATA SOURCE | |
suspend fun getDetailAgentVisitFullResponse( | |
token: String, | |
id: Int | |
): SallyResponseResource<BaseResponse<GetAgentDetailResponse>> { | |
return asSallyResponseResourceSuspend { | |
Log.d("MYTAG", "SUSPEND") | |
agentVisitService.getDetailAgentVisitWithFullResponse( | |
token = token, | |
id = id | |
) | |
} | |
} | |
// REPOSITORY | |
override suspend fun getDetailAgentVisitWithFullResponse(id: Int): | |
SallyResponseResource<BaseResponse<GetAgentDetailResponse>> { | |
return agentVisitDataSource.getDetailAgentVisitFullResponse( | |
token = "", | |
id = id | |
) | |
} | |
// USE CASE | |
override suspend fun getDetailAgentVisitWithFullResponse(id: Int): | |
SallyResponseResource<BaseResponse<GetAgentDetailResponse>> { | |
return iAgentVisitRepository.getDetailAgentVisitWithFullResponse(id) | |
} | |
// VIEWMODEL | |
sealed interface VisitingDetailUiState { | |
object Loading : VisitingDetailUiState | |
data class Error(val error: String) : VisitingDetailUiState | |
data class Success( | |
val agentVisitingDetail: GetAgentDetailResponse? = null | |
) : VisitingDetailUiState | |
} | |
sealed interface DraftDetailUiEvent { | |
data class ShowErrorMessage( | |
val staticError: String?, | |
val dynamicError: String?, | |
val errorCode: String?, | |
val responseBodyError: String? | |
) : DraftDetailUiEvent | |
} | |
private val _errorEventFlow = MutableSharedFlow<DraftDetailUiEvent>(replay = 1) | |
val errorEventFlow = _errorEventFlow.asSharedFlow() | |
private val _uiStateDetail = | |
MutableStateFlow<VisitingDetailUiState>(VisitingDetailUiState.Loading) | |
val uiStateDetail = _uiStateDetail.asStateFlow() | |
fun getAgentVisitDetailFullResponse() { | |
val visitId = savedStateHandle.get<Int>("visitId") | |
viewModelScope.launch { | |
val visitDetail = visitId?.let { useCase.getDetailAgentVisitWithFullResponse(id = it) } | |
when (visitDetail) { | |
is SallyResponseResource.Loading -> { | |
Log.d("MYTAG", "LOADING") | |
_uiStateDetail.value = VisitingDetailUiState.Loading | |
} | |
is SallyResponseResource.Success -> { | |
Log.d("MYTAG", "SUCCESS") | |
_uiStateDetail.value = VisitingDetailUiState.Success(visitDetail.data.data) | |
} | |
is SallyResponseResource.Error -> { | |
Log.d("MYTAG", "ERROR") | |
Log.d("MYTAG", "ERROR 1 ${visitDetail.responseBodyError}") | |
Log.d("MYTAG", "ERROR 2 ${visitDetail.exception.responseBodyError}") | |
val validationError = | |
asResponseErrorResponse(responseBodyError = visitDetail.exception.responseBodyError) | |
validationError.forEach { (field, message) -> | |
Log.d( | |
"MYTAG", | |
"createAgentProspect: ERROR ${"Field: $field, Message: $message"}" | |
) | |
} | |
_eventFlow.emit( | |
VisitingUiEvent.ShowErrorMessageStatic( | |
staticError = visitDetail.exception.message, | |
dynamicError = visitDetail.exception.cause?.message.toString(), | |
errorCode = visitDetail.errorCode, | |
responseBodyError = visitDetail.exception.responseBodyError | |
) | |
) | |
_uiStateDetail.value = | |
VisitingDetailUiState.Error(error = visitDetail.exception.toString()) | |
} | |
else -> { | |
AnalyticsUtils.analytic( | |
Constant.EVENT_FAILURE, | |
"Exception in getAgentVisit Detail" | |
) | |
} | |
} | |
} | |
} | |
// FRAGMENT | |
override fun onViewCreated() { | |
viewModel.getAgentVisitDetailFullResponse() | |
observeVisitDetail() | |
} | |
private fun observeVisitDetail() { | |
viewLifecycleOwner.lifecycleScope.launch { | |
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { | |
viewModel.uiStateDetail.collect { visitingUiState -> | |
when (visitingUiState) { | |
is VisitingDetailUiState.Success -> { | |
Log.d("MYTAG", "SUCCESS FRAGMENT") | |
// HANDLE SUCCESS EVENT | |
} | |
is VisitingDetailUiState.Error -> { | |
Log.d("MYTAG", "ERROR FRAGMENT") | |
viewModel.eventFlow.collectLatest { | |
when (it) { | |
is VisitingUiEvent.ShowErrorMessageStatic -> { | |
Timber.tag("MYTAG") | |
.d("observeVisitDetail: %s", it.staticError) | |
Timber.tag("MYTAG") | |
.d("observeVisitDetail: %s", it.dynamicError) | |
Timber.tag("MYTAG") | |
.d("observeVisitDetail: %s", it.errorCode) | |
Timber.tag("MYTAG") | |
.d("observeVisitDetail: %s", it.responseBodyError) | |
} | |
} | |
} | |
} | |
is VisitingDetailUiState.Loading -> { | |
// HANDLE LOADING EVENT | |
Log.d("MYTAG", "LOADING FRAGMENT") | |
} | |
} | |
} | |
} | |
} | |
} | |
// FLOW FUNCTION | |
// API SERVICE | |
@GET("agent-visit/{id}") | |
suspend fun getDetailAgentVisitWithFullResponse( | |
@Header("Authorization") token: String, | |
@Path(value = "id", encoded = true) id: Int | |
): BaseResponse<GetAgentDetailResponse> | |
// DATA SOURCE | |
fun getDetailAgentVisitFullResponseFlow( | |
token: String, | |
id: Int | |
): Flow<BaseResponse<GetAgentDetailResponse>> { | |
return flow { | |
while (true) { | |
Log.d("MYTAG", "FLOW") | |
val getDetailAgent = agentVisitService.getDetailAgentVisitWithFullResponse( | |
token = token, | |
id = id | |
) | |
emit(getDetailAgent) | |
delay(5000L) | |
} | |
} | |
} | |
// REPOSITORY | |
override fun getDetailAgentVisitFullResponseFlow(id: Int): | |
Flow<SallyResponseResource<BaseResponse<GetAgentDetailResponse>>> { | |
return agentVisitDataSource.getDetailAgentVisitFullResponseFlow( | |
token = "Bearer ${getAccountInfo().token}", | |
id = id | |
).asSallyResponseResourceFlow() | |
} | |
// USE CASE | |
override fun getDetailAgentVisitFullResponseFlow(id: Int): | |
Flow<SallyResponseResource<BaseResponse<GetAgentDetailResponse>>> { | |
return iAgentVisitRepository.getDetailAgentVisitFullResponseFlow(id) | |
} | |
// VIEWMODEL | |
sealed interface VisitingDetailUiState { | |
object Loading : VisitingDetailUiState | |
data class Error(val error: String) : VisitingDetailUiState | |
data class Success( | |
val agentVisitingDetail: GetAgentDetailResponse? = null | |
) : VisitingDetailUiState | |
} | |
private val _uiStateDetail = | |
MutableStateFlow<VisitingDetailUiState>(VisitingDetailUiState.Loading) | |
val uiStateDetail = _uiStateDetail.asStateFlow() | |
// if viewmodelScope | |
fun getAgentVisitDetailFullResponseFlow() { | |
val visitId = savedStateHandle.get<Int>("visitId") | |
viewModelScope.launch { | |
if (visitId != null) { | |
useCase.getDetailAgentVisitFullResponseFlow(id = visitId) | |
.collectLatest { visitDetail -> | |
when (visitDetail) { | |
is SallyResponseResource.Loading -> { | |
Log.d("MYTAG", "LOADING") | |
_uiStateDetail.value = VisitingDetailUiState.Loading | |
} | |
is SallyResponseResource.Success -> { | |
Log.d("MYTAG", "SUCCESS") | |
_uiStateDetail.value = | |
VisitingDetailUiState.Success(visitDetail.data.data) | |
} | |
is SallyResponseResource.Error -> { | |
Log.d("MYTAG", "ERROR") | |
_eventFlow.emit( | |
VisitingUiEvent.ShowErrorMessageStatic( | |
staticError = visitDetail.exception.message, | |
dynamicError = visitDetail.exception.cause?.message.toString(), | |
errorCode = visitDetail.errorCode, | |
responseBodyError = visitDetail.exception.responseBodyError | |
) | |
) | |
_uiStateDetail.value = | |
VisitingDetailUiState.Error(error = visitDetail.exception.toString()) | |
} | |
} | |
} | |
} | |
} | |
} | |
// if StateIn | |
val agentDetail: StateFlow<VisitingDetailUiState>? = | |
visitId?.let { | |
useCase.getDetailAgentVisitFullResponseFlow(id = it).map { visitDetail -> | |
Log.d("MYTAG", ": got exec") | |
when (visitDetail) { | |
is SallyResponseResource.Loading -> { | |
Log.d("MYTAG", "LOADING") | |
VisitingDetailUiState.Loading | |
} | |
is SallyResponseResource.Success -> { | |
Log.d("MYTAG", "SUCCESS") | |
VisitingDetailUiState.Success(visitDetail.data.data) | |
} | |
is SallyResponseResource.Error -> { | |
Log.d("MYTAG", "ERROR") | |
_eventFlow.emit( | |
VisitingUiEvent.ShowErrorMessageStatic( | |
staticError = visitDetail.exception.message, | |
dynamicError = visitDetail.exception.cause?.message.toString(), | |
errorCode = visitDetail.errorCode, | |
responseBodyError = visitDetail.exception.responseBodyError | |
) | |
) | |
VisitingDetailUiState.Error(error = visitDetail.exception.toString()) | |
} | |
} | |
}.stateIn( | |
viewModelScope, | |
SharingStarted.WhileSubscribed(5_000), | |
VisitingDetailUiState.Loading | |
) | |
} | |
// FRAGMENT | |
override fun onViewCreated() { | |
if (stateIn) { | |
observeVisitDetailStateIn() | |
} else { | |
viewModel.getAgentVisitDetailFullResponseFlow() | |
observeVisitDetailBasis() | |
} | |
} | |
private fun observeVisitDetailBasic() { | |
viewLifecycleOwner.lifecycleScope.launch { | |
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { | |
viewModel.uiStateDetail.collect { visitingUiState -> | |
when (visitingUiState) { | |
is VisitingDetailUiState.Success -> { | |
Log.d("MYTAG", "SUCCESS FRAGMENT") | |
// HANDLE SUCCESS EVENT | |
} | |
is VisitingDetailUiState.Error -> { | |
Log.d("MYTAG", "ERROR FRAGMENT") | |
viewModel.eventFlow.collectLatest { | |
when (it) { | |
is VisitingUiEvent.ShowErrorMessageStatic -> { | |
Timber.tag("MYTAG") | |
.d("observeVisitDetail: %s", it.staticError) | |
Timber.tag("MYTAG") | |
.d("observeVisitDetail: %s", it.dynamicError) | |
Timber.tag("MYTAG") | |
.d("observeVisitDetail: %s", it.errorCode) | |
Timber.tag("MYTAG") | |
.d("observeVisitDetail: %s", it.responseBodyError) | |
} | |
} | |
} | |
} | |
is VisitingDetailUiState.Loading -> { | |
// HANDLE LOADING EVENT | |
Log.d("MYTAG", "LOADING FRAGMENT") | |
} | |
} | |
} | |
} | |
} | |
} | |
private fun observeVisitDetailStateIn() { | |
viewLifecycleOwner.lifecycleScope.launch { | |
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { | |
viewModel.uiStateDetail.collect { visitingUiState -> | |
when (visitingUiState) { | |
is VisitingDetailUiState.Success -> { | |
// HANDLE SUCCESS EVENT | |
} | |
is VisitingDetailUiState.Error -> { | |
viewModel.eventFlow.collectLatest { | |
when (it) { | |
is VisitingUiEvent.ShowErrorMessageStatic -> { | |
Timber.tag("MYTAG").d("observeVisitDetail: %s", it.staticError) | |
Timber.tag("MYTAG").d("observeVisitDetail: %s", it.dynamicError) | |
Timber.tag("MYTAG").d("observeVisitDetail: %s", it.errorCode) | |
Timber.tag("MYTAG").d("observeVisitDetail: %s", it.responseBodyError) | |
} | |
} | |
} | |
} | |
is VisitingDetailUiState.Loading -> { | |
// HANDLE LOADING EVENT | |
} | |
} | |
} | |
} | |
} | |
} | |
// PAGING | |
// API SERVICE | |
@GET("agent-visit") | |
suspend fun getListAgentVisitFullResponse( | |
@Header("Authorization") token: String, | |
@QueryMap body: MutableMap<String, Any>, | |
): BaseResponse<List<GetAgentVisitResponse>> | |
// DATA SOURCE | |
suspend fun getListAgentVisitFullResponse( | |
token: String, | |
body: GetAgentVisitRequest, | |
): BaseResponse<List<GetAgentVisitResponse>> { | |
return agentVisitService.getListAgentVisitFullResponse( | |
token = token, | |
body = convertDataClassToMap(body) | |
) | |
} | |
// PAGING SOURCE | |
class AgentVisitListPagingDataSource( | |
private val agentVisitDataSource: AgentVisitDataSource, | |
private val getAgentVisitRequest: GetAgentVisitRequest, | |
private val token: String | |
) : PagingSource<Int, GetAgentVisitResponse>() { | |
override fun getRefreshKey(state: PagingState<Int, GetAgentVisitResponse>): Int? { | |
Log.d("MYTAG", "paging source call: ") | |
return state.anchorPosition?.let { anchorPosition -> | |
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) | |
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) | |
} | |
} | |
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, GetAgentVisitResponse> { | |
Log.d("MYTAG", "paging source load: ") | |
return try { | |
Log.d("MYTAG", "paging source load: success") | |
val nextPageNumber = params.key ?: 1 | |
val pageSize = params.loadSize | |
val bodyWithSkip = getAgentVisitRequest.copy(skip = (nextPageNumber - 1) * pageSize) | |
val getListResponse = agentVisitDataSource.getListAgentVisitFullResponse( | |
token = token, | |
body = bodyWithSkip | |
) | |
val data = getListResponse.data ?: emptyList() | |
val nextKey = if (data.isEmpty()) { | |
null | |
} else { | |
nextPageNumber + 1 | |
} | |
LoadResult.Page( | |
data = data, | |
prevKey = if (nextPageNumber == 1) null else nextPageNumber - 1, | |
nextKey = nextKey | |
) | |
} catch (e: IOException) { | |
print("Error: ${e.message}") | |
Log.d("MYTAG", "paging source error IOexcetpion: ") | |
LoadResult.Error(e) | |
} catch (e: Exception) { | |
println("${e.message}") | |
Log.d("MYTAG", "paging source error excetpion: $e") | |
LoadResult.Error(e) | |
} catch (e: HttpException) { | |
println("${e.message}") | |
Log.d("MYTAG", "paging source error excetpion: $e") | |
LoadResult.Error(e) | |
} | |
} | |
} | |
// REPOSITORY | |
override fun getListAgentVisitWithFullResponse(body: GetAgentVisitRequest): | |
Flow<PagingData<GetAgentVisitResponse>> = | |
Pager( | |
config = PagingConfig( | |
pageSize = 20, | |
prefetchDistance = 10, | |
), | |
pagingSourceFactory = { | |
AgentVisitListPagingDataSource( | |
agentVisitDataSource = agentVisitDataSource, | |
token = "", | |
getAgentVisitRequest = body | |
) | |
} | |
).flow | |
// USE CASE | |
override fun getListAgentVisitWithFullResponse(body: GetAgentVisitRequest): | |
Flow<PagingData<GetAgentVisitResponse>> { | |
return iAgentVisitRepository.getListAgentVisitWithFullResponse( | |
body = body | |
) | |
} | |
// VIEWMODEL | |
data class ListAgentVisit( | |
val agentList: Flow<PagingData<GetAgentVisitResponse>>, | |
) { | |
companion object { | |
val default: ListAgentVisit = ListAgentVisit( | |
agentList = emptyFlow(), | |
) | |
} | |
} | |
data class FilterAgentState( | |
val search: String, | |
val prospectConsument: String, | |
val agentProfessionCode: String, | |
val startDate: String, | |
val endDate: String | |
) { | |
companion object { | |
val default: FilterAgentState = FilterAgentState( | |
search = "", | |
prospectConsument = "", | |
agentProfessionCode = "", | |
startDate = "", | |
endDate = "" | |
) | |
} | |
} | |
private val filterAgentState = MutableStateFlow(FilterAgentState.default) | |
val getListAgent: StateFlow<ListAgentVisit> = filterAgentState.flatMapLatest { filterState -> | |
flow { | |
val agentList = useCase.getListAgentVisitWithFullResponse( | |
body = GetAgentVisitRequest( | |
branchCode = useCase.getAccountInfo().branchCode, | |
moId = useCase.getAccountInfo().employeeId, | |
skip = 1, | |
search = filterState.search, | |
prospectConsument = filterState.prospectConsument, | |
agentProfessionCode = filterState.agentProfessionCode, | |
startDate = filterState.startDate, | |
endDate = filterState.endDate | |
) | |
).cachedIn(viewModelScope) | |
emit(ListAgentVisit(agentList = agentList)) | |
} | |
}.stateIn( | |
viewModelScope, | |
SharingStarted.WhileSubscribed(5_000), | |
ListAgentVisit.default | |
) | |
fun updateFilterState( | |
search: String? = null, | |
prospectConsument: String? = null, | |
agentProfessionCode: String? = null, | |
startDate: String? = null, | |
endDate: String? = null | |
) { | |
val currentFilter = filterAgentState.value | |
val updatedFilter = currentFilter.copy( | |
search = search ?: currentFilter.search, | |
prospectConsument = prospectConsument ?: currentFilter.prospectConsument, | |
agentProfessionCode = agentProfessionCode ?: currentFilter.agentProfessionCode, | |
startDate = startDate ?: currentFilter.startDate, | |
endDate = endDate ?: currentFilter.endDate | |
) | |
filterAgentState.value = updatedFilter | |
} | |
fun resetFilterState() { | |
filterAgentState.value = FilterAgentState.default | |
} | |
// FRAGMENT | |
override fun onViewCreated() { | |
setupRecyclerView() | |
setupList() | |
} | |
// example using filter state | |
viewModel.search.value = query | |
viewModel.updateFilterState( | |
search = query | |
) | |
private fun setupRecyclerView() { | |
Log.d("MYTAG", "paging initial fragment") | |
val navController = findNavController() | |
agentVisitAdapter = AgentVisitAdapter( | |
viewModel = viewModel, | |
navController = navController | |
) | |
binding.lstAgentVisiting.adapter = agentVisitAdapter | |
viewLifecycleOwner.lifecycleScope.launch { | |
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { | |
viewModel.getListAgent.collectLatest { | |
Log.d("MYTAG", "paging fragment:") | |
it.agentList.collectLatest { paging -> | |
agentVisitAdapter.submitData(paging) | |
} | |
} | |
} | |
} | |
} | |
private fun setupList() { | |
val navController = findNavController() | |
agentVisitAdapter = AgentVisitAdapter( | |
viewModel = viewModel, | |
navController = navController | |
) | |
binding.lstAgentVisiting.apply { | |
layoutManager = LinearLayoutManager(context) | |
adapter = agentVisitAdapter.withLoadStateHeaderAndFooter( | |
header = PagingLoadStateAdapter { agentVisitAdapter.retry() }, | |
footer = PagingLoadStateAdapter { agentVisitAdapter.retry() } | |
) | |
setHasFixedSize(true) | |
viewLifecycleOwner.lifecycleScope.launch { | |
agentVisitAdapter.loadStateFlow.collectLatest { loadState -> | |
when (loadState.refresh) { | |
is LoadState.Loading -> { | |
binding.lytShimmerAgent.root.visible(isVisible = loadState.refresh is LoadState.Loading) | |
binding.chipsFilterShimmer.root.visible(isVisible = loadState.refresh is LoadState.Loading) | |
} | |
is LoadState.Error -> { | |
val error = (loadState.refresh as LoadState.Error).error | |
val loadStateError: LoadStateError = handleLoadStatePagingError(cause = error) | |
val errorCode: Int? = loadStateError.errorCode | |
val errorMessage: String? = loadStateError.errorMessage | |
val responseBodyError: String? = loadStateError.responseBodyError | |
val errorMessageStatic: String? = loadStateError.errorMessageStatic | |
Log.d("MYTAG", "setupList: $errorCode") | |
Log.d("MYTAG", "setupList: $errorMessage") | |
Log.d("MYTAG", "setupList: $responseBodyError") | |
Log.d("MYTAG", "setupList: $errorMessageStatic") | |
} | |
is LoadState.NotLoading -> { | |
binding.lstAgentVisiting.isVisible = true | |
} | |
} | |
// if (loadState.source.append.endOfPaginationReached && agentVisitAdapter.itemCount != 0) | |
// requireContext().toast(context.getString(com.kreditplus.sally.core.R.string.data_pagination_loaded)) | |
// with(binding) { | |
// lytShimmerAgent.root.visible(loadState.refresh is LoadState.Loading && agentVisitAdapter.itemCount == 0) | |
// chipsFilterShimmer.root.visible(loadState.refresh is LoadState.Loading && agentVisitAdapter.itemCount == 0) | |
// lstAgentVisiting.isVisible = agentVisitAdapter.itemCount > 0 | |
// lytEmptyAgent.root.visible(loadState.refresh is LoadState.NotLoading && agentVisitAdapter.itemCount == 0) | |
// if (agentVisitAdapter.itemCount == 0) { | |
// with(binding.lytEmptyAgent) { | |
// lblTitleEmpty.text = | |
// getString(R.string.empty_state_title_agent) | |
// lblMessageEmpty.text = | |
// getString(R.string.empty_state_label_agent) | |
// } | |
// } | |
// val errorState = when (loadState.refresh) { | |
// is LoadState.Error -> { | |
// loadState.refresh as LoadState.Error | |
// } | |
// | |
// else -> null | |
// } | |
// errorState?.let { errorStates -> | |
// if (agentVisitAdapter.itemCount == 0) { | |
// binding.lytEmptyAgent.apply { | |
// if (!errorStates.error.message.isNullOrEmpty()) { | |
// lblTitleEmpty.text = | |
// getString(R.string.error_state_title_agent) | |
// lblMessageEmpty.text = errorStates.error.message | |
// requireContext().toast(errorStates.error.message.orEmpty()) | |
// } | |
// } | |
// } | |
// } | |
// if (loadState.refresh is LoadState.Loading) lstAgentVisiting.scrollToPosition( | |
// 0 | |
// ) | |
// } | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment