I am trying to display data on two different fragments from viewmodels: Recent Races and Upcoming Races. i wrote a filter function to filter the races that are yet to start and the ones that finished. Upcoming races works perfectly, when there is a change in api endpoint it removes the race from upcoming races list. but the problem is that it wont add it to recent races.
here is my code in RecentRacesViewModel
private fun getDetails() {
getRaceDetailsUseCase().onEach { result ->
when (result) {
is Resource.Success -> {
val filteredList = result.data.filter {
val time = Calendar.getInstance().time
val formatterCurrentTime = SimpleDateFormat("yyyy-MM-dd")
val formatterNow = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val currentTime = formatterCurrentTime.format(time)
val dateNow = LocalDate.parse(currentTime, formatterNow)
val dateFromModel = it.date
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val date = LocalDate.parse(dateFromModel, formatter)
dateNow >= date
}
_state1.value = Resource.Success(filteredList)
}
is Resource.Error -> {
_state1.value = Resource.Error("woops!")
}
is Resource.Loading -> {
_state1.value = Resource.Loading(true)
}
}
}.launchIn(viewModelScope)
}
thanks for help
Edit: adding the UseCase:
class RaceDetailsUseCase #Inject constructor(
private val repository: RaceResultsRepository
) {
operator fun invoke(): Flow<Resource<List<RaceDomain>>> = flow {
try {
emit(Resource.Loading(true))
val raceData = repository.GetRaceResultsRepository()
emit(Resource.Success(raceData))
} catch (e: HttpException) {
Log.d("tag", "error")
} catch (e: IOException) {
Log.d("tag", "io error")
}
}
}
I am not getting weekly data from google fit (Yes, There is data in google fit which I have track using watch), but Yes I am getting today's date data.
I am attaching code snippet.
Start date = "2022-09-13T00:00:00Z"
End date = "2022-09-20T23:59:59Z"
private fun readSleepSessions(startTime : Long , endTime : Long) {
val client = Fitness.getSessionsClient(requireContext(), getGoogleAccount())
val sessionReadRequest = SessionReadRequest.Builder()
.read(DataType.TYPE_SLEEP_SEGMENT)
.includeSleepSessions()
.readSessionsFromAllApps()
.enableServerQueries()
.setTimeInterval(1663027200000, 1663718399000, TimeUnit.MILLISECONDS)
.build()
client.readSession(sessionReadRequest)
.addOnSuccessListener {
Log.d(TAG, "readSleepSessions: $sessionReadRequest")
dumpSleepSessions(it)
}
.addOnFailureListener {
Log.e("MainScreen", "Unable to read sleep sessions", it)
}
}
private fun dumpSleepSessions(response: SessionReadResponse) {
if (response.sessions.isNotEmpty()){
for (session in response.sessions) {
dumpSleepSession(session, response.getDataSet(session))
Log.d(TAG, "dumpSleepSessions: ${response.sessions}")
}
}else{
Log.d(TAG, "dumpSleepSessionsResponse: ${response.status}")
}
}
private fun dumpSleepSession(session: Session, dataSets: List<DataSet>) {
dumpSleepSessionMetadata(session)
dumpSleepDataSets(dataSets)
}
private fun dumpSleepDataSets(dataSets: List<DataSet>) {
for (dataSet in dataSets) {
for (dataPoint in dataSet.dataPoints) {
val sleepStageOrdinal = dataPoint.getValue(Field.FIELD_SLEEP_SEGMENT_TYPE).asInt()
val sleepStage = sleepTargetName[sleepStageOrdinal]
val durationMillis =
dataPoint.getEndTime(TimeUnit.MILLISECONDS) - dataPoint.getStartTime(TimeUnit.MILLISECONDS)
val duration = TimeUnit.MILLISECONDS.toMinutes(durationMillis)
Log.d(TAG, "\t$sleepStage: $duration (minutes)")
}
}
I know that there are a lot of posts "How to cancel Coroutines Scope" but I couldn't find the answer for my case.
I have an Array of objects that I want to send each of them to Server using Coroutines.
What I need is, if one of my requests returns error, canceling others.
Here is my code:
private fun sendDataToServer(function: () -> Unit) {
LiabilitiesWizardSessionManager.getLiabilityAddedDocuments().let { documents ->
if (documents.isEmpty().not()) {
CoroutineScope(Dispatchers.IO).launch {
documents.mapIndexed { index, docDetail ->
async {
val result = uploadFiles(docDetail)
}
}.map {
var result = it.await()
}
}
} else function.invoke()
}
}
Below is my uploadFiles() function:
private suspend fun uploadFiles(docDetail: DocDetail): ArchiveFileResponse? {
LiabilitiesWizardSessionManager.mCreateLiabilityModel.let { model ->
val file = File(docDetail.fullFilePath)
val crmCode = docDetail.docTypeCode
val desc = docDetail.docTypeDesc
val id = model.commitmentMember?.id
val idType = 1
val createArchiveFileModel = CreateArchiveFileModel(108, desc, id, idType).apply {
this.isLiability = true
this.adaSystem = 3
}
val result = mRepositoryControllerKotlin.uploadFile(file, createArchiveFileModel)
return when (result) {
is ResultWrapper.Success -> {
result.value
}
is ResultWrapper.GenericError -> {
null
}
is ResultWrapper.NetworkError -> {
null
}
}
}
}
I know, I'm missing something.
I'm trying to identify the type of cellular connection.
I've used different methods, like for example the one suggested here, but I keep getting 4G as a result, on a Samsung with Android 10 and 5G connection.
How is it possible to read the correct network type?
private fun getNetworkType(telephonyManager: TelephonyManager): String {
return when (telephonyManager.networkType) {
TelephonyManager.NETWORK_TYPE_UNKNOWN -> "unknown"
TelephonyManager.NETWORK_TYPE_GPRS,
TelephonyManager.NETWORK_TYPE_EDGE,
TelephonyManager.NETWORK_TYPE_CDMA,
TelephonyManager.NETWORK_TYPE_1xRTT,
TelephonyManager.NETWORK_TYPE_IDEN,
TelephonyManager.NETWORK_TYPE_GSM -> "2G"
TelephonyManager.NETWORK_TYPE_UMTS,
TelephonyManager.NETWORK_TYPE_EVDO_0,
TelephonyManager.NETWORK_TYPE_EVDO_A,
TelephonyManager.NETWORK_TYPE_HSDPA,
TelephonyManager.NETWORK_TYPE_HSUPA,
TelephonyManager.NETWORK_TYPE_HSPA,
TelephonyManager.NETWORK_TYPE_EVDO_B,
TelephonyManager.NETWORK_TYPE_EHRPD,
TelephonyManager.NETWORK_TYPE_HSPAP,
TelephonyManager.NETWORK_TYPE_TD_SCDMA -> "3G"
TelephonyManager.NETWORK_TYPE_LTE,
TelephonyManager.NETWORK_TYPE_IWLAN -> "4G"
TelephonyManager.NETWORK_TYPE_NR -> "5G"
else -> "something else"
}
}
private fun getRadioTechnology(telephonyManager: TelephonyManager): String {
try {
val obj = Class.forName(telephonyManager.javaClass.name)
.getDeclaredMethod("getServiceState", *arrayOfNulls(0)).invoke(telephonyManager, *arrayOfNulls(0))
val methods: Array<Method> = Class.forName(obj.javaClass.name).declaredMethods
for (method in methods) {
if (method.name == "getRadioTechnology" ) {
method.isAccessible = true
val radioTechnology = (method.invoke(obj) as Int).toInt()
return "$radioTechnology"
}
}
} catch (e: Exception) {
Log.e("Test5G", "", e)
}
return ""
}
#SuppressLint("MissingPermission")
fun getActiveSubscriptionInfoList(): String {
val subscriptionInfos = SubscriptionManager.from(this).activeSubscriptionInfoList
var ret: String = ""
for(sub in subscriptionInfos) {
val id = sub.subscriptionId
val telephonyManager = telephonyManager.createForSubscriptionId(id);
ret += getRadioTechnology(telephonyManager)
}
return ret
}
This is how I did it:
telephonyManager.listen(object : PhoneStateListener() {
override fun onServiceStateChanged(serviceState: ServiceState) {
val isNrAvailable = serviceState.toString().isNrAvailable()
// use isNrAvailable
}
}, PhoneStateListener.LISTEN_SERVICE_STATE)
Where
fun String.isNrAvailable() =
contains("nrState=CONNECTED") ||
contains("nsaState=5"))
I am using kotlin coroutines for network request using extension method to call class in retrofit like this
public suspend fun <T : Any> Call<T>.await(): T {
return suspendCancellableCoroutine { continuation ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>?, response: Response<T?>) {
if (response.isSuccessful) {
val body = response.body()
if (body == null) {
continuation.resumeWithException(
NullPointerException("Response body is null")
)
} else {
continuation.resume(body)
}
} else {
continuation.resumeWithException(HttpException(response))
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
// Don't bother with resuming the continuation if it is already cancelled.
if (continuation.isCancelled) return
continuation.resumeWithException(t)
}
})
registerOnCompletion(continuation)
}
}
then from calling side i am using above method like this
private fun getArticles() = launch(UI) {
loading.value = true
try {
val networkResult = api.getArticle().await()
articles.value = networkResult
}catch (e: Throwable){
e.printStackTrace()
message.value = e.message
}finally {
loading.value = false
}
}
i want to exponential retry this api call in some case i.e (IOException) how can i achieve it ??
I would suggest to write a helper higher-order function for your retry logic. You can use the following implementation for a start:
suspend fun <T> retryIO(
times: Int = Int.MAX_VALUE,
initialDelay: Long = 100, // 0.1 second
maxDelay: Long = 1000, // 1 second
factor: Double = 2.0,
block: suspend () -> T): T
{
var currentDelay = initialDelay
repeat(times - 1) {
try {
return block()
} catch (e: IOException) {
// you can log an error here and/or make a more finer-grained
// analysis of the cause to see if retry is needed
}
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
}
return block() // last attempt
}
Using this function is very strightforward:
val networkResult = retryIO { api.getArticle().await() }
You can change retry parameters on case-by-case basis, for example:
val networkResult = retryIO(times = 3) { api.doSomething().await() }
You can also completely change the implementation of retryIO to suit the needs of your application. For example, you can hard-code all the retry parameters, get rid of the limit on the number of retries, change defaults, etc.
Here an example with the Flow and the retryWhen function
RetryWhen Extension :
fun <T> Flow<T>.retryWhen(
#FloatRange(from = 0.0) initialDelay: Float = RETRY_INITIAL_DELAY,
#FloatRange(from = 1.0) retryFactor: Float = RETRY_FACTOR_DELAY,
predicate: suspend FlowCollector<T>.(cause: Throwable, attempt: Long, delay: Long) -> Boolean
): Flow<T> = this.retryWhen { cause, attempt ->
val retryDelay = initialDelay * retryFactor.pow(attempt.toFloat())
predicate(cause, attempt, retryDelay.toLong())
}
Usage :
flow {
...
}.retryWhen { cause, attempt, delay ->
delay(delay)
...
}
Here's a more sophisticated and convenient version of my previous answer, hope it helps someone:
class RetryOperation internal constructor(
private val retries: Int,
private val initialIntervalMilli: Long = 1000,
private val retryStrategy: RetryStrategy = RetryStrategy.LINEAR,
private val retry: suspend RetryOperation.() -> Unit
) {
var tryNumber: Int = 0
internal set
suspend fun operationFailed() {
tryNumber++
if (tryNumber < retries) {
delay(calculateDelay(tryNumber, initialIntervalMilli, retryStrategy))
retry.invoke(this)
}
}
}
enum class RetryStrategy {
CONSTANT, LINEAR, EXPONENTIAL
}
suspend fun retryOperation(
retries: Int = 100,
initialDelay: Long = 0,
initialIntervalMilli: Long = 1000,
retryStrategy: RetryStrategy = RetryStrategy.LINEAR,
operation: suspend RetryOperation.() -> Unit
) {
val retryOperation = RetryOperation(
retries,
initialIntervalMilli,
retryStrategy,
operation,
)
delay(initialDelay)
operation.invoke(retryOperation)
}
internal fun calculateDelay(tryNumber: Int, initialIntervalMilli: Long, retryStrategy: RetryStrategy): Long {
return when (retryStrategy) {
RetryStrategy.CONSTANT -> initialIntervalMilli
RetryStrategy.LINEAR -> initialIntervalMilli * tryNumber
RetryStrategy.EXPONENTIAL -> 2.0.pow(tryNumber).toLong()
}
}
Usage:
coroutineScope.launch {
retryOperation(3) {
if (!tryStuff()) {
Log.d(TAG, "Try number $tryNumber")
operationFailed()
}
}
}
Flow Version https://github.com/hoc081098/FlowExt
package com.hoc081098.flowext
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.retryWhen
#ExperimentalTime
public fun <T> Flow<T>.retryWithExponentialBackoff(
initialDelay: Duration,
factor: Double,
maxAttempt: Long = Long.MAX_VALUE,
maxDelay: Duration = Duration.INFINITE,
predicate: suspend (cause: Throwable) -> Boolean = { true }
): Flow<T> {
require(maxAttempt > 0) { "Expected positive amount of maxAttempt, but had $maxAttempt" }
return retryWhenWithExponentialBackoff(
initialDelay = initialDelay,
factor = factor,
maxDelay = maxDelay
) { cause, attempt -> attempt < maxAttempt && predicate(cause) }
}
#ExperimentalTime
public fun <T> Flow<T>.retryWhenWithExponentialBackoff(
initialDelay: Duration,
factor: Double,
maxDelay: Duration = Duration.INFINITE,
predicate: suspend FlowCollector<T>.(cause: Throwable, attempt: Long) -> Boolean
): Flow<T> = flow {
var currentDelay = initialDelay
retryWhen { cause, attempt ->
predicate(cause, attempt).also {
if (it) {
delay(currentDelay)
currentDelay = (currentDelay * factor).coerceAtMost(maxDelay)
}
}
}.let { emitAll(it) }
}
You can try this simple but very agile approach with simple usage:
EDIT: added a more sophisticated solution in a separate answer.
class Completion(private val retry: (Completion) -> Unit) {
fun operationFailed() {
retry.invoke(this)
}
}
fun retryOperation(retries: Int,
dispatcher: CoroutineDispatcher = Dispatchers.Default,
operation: Completion.() -> Unit
) {
var tryNumber = 0
val completion = Completion {
tryNumber++
if (tryNumber < retries) {
GlobalScope.launch(dispatcher) {
delay(TimeUnit.SECONDS.toMillis(tryNumber.toLong()))
operation.invoke(it)
}
}
}
operation.invoke(completion)
}
The use it like this:
retryOperation(3) {
if (!tryStuff()) {
// this will trigger a retry after tryNumber seconds
operationFailed()
}
}
You can obviously build more on top of it.