Android - HealthConnectClient.readRecords or aggregateGroupByPeriod takes forever - android

Below codes takes forever to execute. Get it from official docs. HealthConnect is installed on the device.I believe there is an inner error that i cant see in logs of IDE. Code is called from java class.
What can cause this?
companion object {
fun readStepsByTimeRange(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) : CompletableFuture<Long> = GlobalScope.future {
innerReadStepsByTimeRange(healthConnectClient, startTime, endTime)
}
private suspend fun innerReadStepsByTimeRange (
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) : Long {
/*var totalStepsCount = 0L
val response =
healthConnectClient.readRecords(
ReadRecordsRequest(
StepsRecord::class,
timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
)
)
for (stepRecord in response.records) {
totalStepsCount += stepRecord.count;
}
return totalStepsCount*/
val startTime = LocalDateTime.now().minusMonths(1)
val endTime = LocalDateTime.now()
try {
val response =
healthConnectClient.aggregateGroupByPeriod(
AggregateGroupByPeriodRequest(
metrics = setOf(StepsRecord.COUNT_TOTAL),
timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
timeRangeSlicer = Period.ofDays(1)
)
)
return response[0].result[StepsRecord.COUNT_TOTAL]!!
}catch (e: Exception) {
return -1
}
}
}
Java caller code below
CompletableFuture<Long> totalStepsCountCF = StepReaderUtils.Companion.readStepsByTimeRange(
healthConnectClient, startDate, endDate);
return totalStepsCountCF.get();
build.gradle
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.0'
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0'
implementation "androidx.health.connect:connect-client:1.0.0-alpha08"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.4"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"

Related

List wont display properly

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")
}
}
}

Kotlin Exception when mocking suspend function java.io.EOFException: Premature end of stream: expected 1 bytes

I use KTor and Kotlin Serialization library in my android project, along with mockk and junit.jupiter for unit testing. I've encountered a problem when mocking ktor's suspend function readText(). The following unit test tests that initErrorMessage() function returns correct error message.
Test class:
class ErrorTest {
private val errorMessage = "objectId must be provided."
private val errorCode = 2689
private val correctResponseJson = "{\"code\":$errorCode,\"message\":\"$errorMessage\"}"
// ResponseException class is from ktor library
private val exceptionMock: ResponseException = mockk(relaxed = true)
#Test
fun `initErrorMessage should return correct error message`() = runTest {
coEvery { exceptionMock.response.readText() } returns correctResponseJson // <-- here is the Error occurs
val expectedError = errorMessage
val actualError = initErrorMessage(exceptionMock)
assertEquals(expectedError, actualError)
}
}
Method to test:
suspend fun initErrorMessage(cause: ResponseException): String {
return try {
val body = cause.response.readText()
val jsonSerializer = JsonObject.serializer()
val jsonObj = Json.decodeFromString(jsonSerializer, body)
jsonObj["message"].toString()
} catch (e: Exception) {
""
}
}
During execution of the first line in the test method I get an Error:
Premature end of stream: expected 1 bytes
java.io.EOFException: Premature end of stream: expected 1 bytes
at io.ktor.utils.io.core.StringsKt.prematureEndOfStream(Strings.kt:492)
at io.ktor.utils.io.core.internal.UnsafeKt.prepareReadHeadFallback(Unsafe.kt:78)
at io.ktor.utils.io.core.internal.UnsafeKt.prepareReadFirstHead(Unsafe.kt:61)
at io.ktor.utils.io.charsets.CharsetJVMKt.decode(CharsetJVM.kt:556)
at io.ktor.utils.io.charsets.EncodingKt.decode(Encoding.kt:103)
at io.ktor.utils.io.charsets.EncodingKt.decode$default(Encoding.kt:101)
at io.ktor.client.statement.HttpStatementKt.readText(HttpStatement.kt:173)
at io.ktor.client.statement.HttpStatementKt.readText$default(HttpStatement.kt:168)
at com.example.android.http.error.ErrorTest$initErrorMessage should return correct error message$1$1.invokeSuspend(ErrorTest.kt:37)
at com.example.android.http.error.ErrorTest$initErrorMessage should return correct error message$1$1.invoke(ErrorTest.kt)
at com.example.android.http.error.ErrorTest$initErrorMessage should return correct error message$1$1.invoke(ErrorTest.kt)
at io.mockk.impl.eval.RecordedBlockEvaluator$record$block$2$1.invokeSuspend(RecordedBlockEvaluator.kt:28)
at io.mockk.impl.eval.RecordedBlockEvaluator$record$block$2$1.invoke(RecordedBlockEvaluator.kt)
at io.mockk.InternalPlatformDsl$runCoroutine$1.invokeSuspend(InternalPlatformDsl.kt:20)
How to mock this suspend method readText() without an Error?
It turned out that the function readText() hasn't been mocked properly.
It is an extension function on HttpResponse and it has to be mocked using mockkStatic function, for example like this:
#BeforeEach
fun setup() {
mockkStatic(HttpResponse::readText)
}
setup() will be executed before each #Test, because it is marked with #BeforeEach annotation.
You can mock the HttpClientCall instead of the ResponseException to create an instance of the ResponseException without mocking (to avoid EOFException).
class ErrorTest {
private val errorMessage = "objectId must be provided."
private val errorCode = 2689
private val correctResponseJson = "{\"code\":$errorCode,\"message\":\"$errorMessage\"}"
#OptIn(InternalAPI::class)
#Test
fun `initErrorMessage should return correct error message`(): Unit = runBlocking {
val responseData = HttpResponseData(
statusCode = HttpStatusCode.OK,
requestTime = GMTDate.START,
headers = Headers.Empty,
version = HttpProtocolVersion.HTTP_1_1,
"",
coroutineContext
)
val call = mockk<HttpClientCall>(relaxed = true) {
// This is how a body received under the hood
coEvery { receive<Input>() } returns BytePacketBuilder().apply { writeText(correctResponseJson) }.build()
// There are cyclic dependencies between HttpClientCall and HttpResponse so it's not possible to mock it in place
every { response } returns DefaultHttpResponse(this, responseData)
}
val exception = ResponseException(call.response, "")
val expectedError = errorMessage
val actualError = initErrorMessage(exception)
assertEquals(expectedError, actualError)
}
}

Kotlin: CoroutineScope is collecting data even after flow is cancelled

I am trying to launch an Coroutine inside my PagingSource in order to watch how long my paging source is already trying to get my data. The only problem I have here is, that my Coroutine is still somehow collecting some data, even after I stopped my shopPagingWatcher Flow. Because of this, it throws IOException("No Intenet Exception) even when it should not.
I am launching a Coroutine because watching the state should not block the main flow of my paging source
PagingSource
class ShopRemoteMediator #Inject constructor(
private val db: FirebaseFirestore,
private val shopPagingWatcher: ShopPagingWatcher,
) : PagingSource<QuerySnapshot, Product>() {
#InternalCoroutinesApi
override suspend fun load(params: LoadParams<QuerySnapshot>): LoadResult<QuerySnapshot, Product> {
return try {
// Launch Async Coroutine, Observe State, throw IO Exception when not loaded within 5 seconds
shopPagingWatcher.start()
CoroutineScope(Dispatchers.IO).launch {
shopPagingWatcher.observeMaxTimeReached().collect { maxTimeReached ->
if (maxTimeReached) {
Timber.d("Mediator failed")
throw IOException("No Internet Exception")
}
}
}
val currentPage = params.key ?: db.collection(FIREBASE_PRODUCTS)
.limit(SHOP_LIST_LIMIT)
.get()
.await()
val lastDocumentSnapShot = currentPage.documents[currentPage.size() - 1]
val nextPage = db.collection(FIREBASE_PRODUCTS)
.limit(SHOP_LIST_LIMIT)
.startAfter(lastDocumentSnapShot)
.get()
.await()
// When PagingSource is here, it successfully loaded currentPage and nextPage, therefore stop Watcher
Timber.d("Mediator Sucessfull")
shopPagingWatcher.stop()
LoadResult.Page(
data = currentPage.toObjects(),
prevKey = null,
nextKey = nextPage
)
} catch (e: Exception) {
// IOException should be caught here, but it is not! The app crashed instead!
Timber.d("Mediator Exception ist $e")
LoadResult.Error(e)
}
}
}
ShopPagingWatcher
#Singleton
class ShopPagingWatcher #Inject constructor() : Workwatcher()
Abstract WorkWatcher
abstract class Workwatcher {
private companion object {
private val dispatcher = Dispatchers.IO
private var timeStamp by Delegates.notNull<Long>()
private var running = false
private var manuallyStopped = false
private var finished = false
private const val maxTime: Long = 5000000000L
}
// Push the current timestamp, set running to true
// I don't know if it is necessary to use "synchronized"
#InternalCoroutinesApi
fun start() = synchronized(dispatcher) {
timeStamp = System.nanoTime()
running = true
manuallyStopped = false
finished = false
}
// Manually stop the WorkerHelper
// I don't know if it is necessary to use "synchronized"
#InternalCoroutinesApi
fun stop() = synchronized(dispatcher) {
running = false
manuallyStopped = true
finished = true
Timber.d("Mediator stopped")
}
// Function that observes the time
fun observeMaxTimeReached(): Flow<Boolean> = flow {
// Check if maxTime is not passed with → (System.nanoTime() - timeStamp) <= maxTime
while (running && !finished && !manuallyStopped && (System.nanoTime() - timeStamp) <= maxTime) {
emit(false)
Timber.d("Currenttime is smaller, everything fine")
}
// This will be executed only when the Worker is running longer than maxTime
if (!manuallyStopped && !finished) {
Timber.d("Currenttime bigger, yikes. Stop worker")
emit(true)
running = false
finished = true
return#flow
} else if (finished || manuallyStopped) {
return#flow
}
}.flowOn(dispatcher)
}
How should I change my Coroutine inside my PagingSource in order to achieve my goal? Timber.d("Mediator stopped) gets called.
I appreciate every help, thank you.
Do you need to measure duration? Time is already passing everywhere, you don't need another thread or coroutine to track that. There's measureNanoTime {} that measures how long a code block took to execute.
Do you need to apply a timeout inside a suspending function? There's withTimeout exactly for that. Example:
class ShopRemoteMediator #Inject constructor(
private val db: FirebaseFirestore,
private val shopPagingWatcher: ShopPagingWatcher,
) : PagingSource<QuerySnapshot, Product>() {
#InternalCoroutinesApi
override suspend fun load(
params: LoadParams<QuerySnapshot>
): LoadResult<QuerySnapshot, Product> {
return try {
withTimeout(5, TimeUnit.SECONDS) { // <<<<<<<<<<
val currentPage = ...
val nextPage = ...
LoadResult.Page(
data = currentPage.toObjects(),
prevKey = null,
nextKey = nextPage
)
}
} catch (e: IOException) {
LoadResult.Error(e)
} catch (e: TimeoutCancellationException) { // <<<<<<<<<<
LoadResult.Error(e)
}
}
}

Last extension function is not called

I came across a strange bug and I can't figure out why it occurs. If I invoke my original function, roundToMidnight() is not called and the date is not rounded.
My original function, what doesn't work:
suspend operator fun invoke(reference: Reference) = reference.tagId
?.let { tagRepository.getTag(it) }
?.uploadDate ?: Date()
.apply { time += accountRepository.getAccount().first().defaultExpiryPeriod }
.roundToMidnight()
}
What does work:
suspend operator fun invoke(reference: Reference): Date {
val date = reference.tagId
?.let { tagRepository.getTag(it) }
?.uploadDate ?: Date()
.apply { time += accountRepository.getAccount().first().defaultExpiryPeriod }
return date.roundToMidnight()
}
roundToMidnight() returns a new instance of Date
fun Date.roundToMidnight(): Date {
val calendar = Calendar.getInstance()
calendar.time = this
calendar[Calendar.HOUR_OF_DAY] = 23
calendar[Calendar.MINUTE] = 59
calendar[Calendar.SECOND] = 59
calendar[Calendar.MILLISECOND] = 0
return Date(calendar.timeInMillis)
}
What causes the differences in both functions? I'd say they would be exactly the same and I see myself refactoring the bugless function into the original in a month time, because I forgot this happens.
As suggested by Egor, an expression body is not always the best solution. Rewrote it to this.
suspend operator fun invoke(reference: Reference): Date {
val tag = reference.tagId?.let { tagRepository.getTag(it)}
val uploadDateOrNow = tag?.uploadDate ?: Date()
uploadDateOrNow.time += defaultExpiryPeriod()
return uploadDateOrNow.roundToMidnight()
}
private suspend fun defaultExpiryPeriod() = accountRepository
.getAccount().first()
.defaultExpiryPeriod
Working on a project of my own and boy, do I miss being criticized in PRs ones in a while.

Adding Retrofit callback to unit test

I am trying to to a fairly simple unit test with mockito but having some trouble with the callback. I have been exploring several alternatives and a few seem good including co-routines and the do answer methods with mockito. I have not been able to figure out a good solution yet, any ideas ?
Here is my test ;
//Test for add 5 minutes
#Test
public void testWithArgumentWithDummyReturnObject() throws ParseException {
ParkingMeter testZone = new ParkingMeter();
testZone.setZoneId("21788");
testZone.setMeterName("Train Lot");
Date startDate = dateFormat.parse("09/10/2018 1:00 PM");
Date endDate = dateFormat.parse("09/10/2018 1:05 PM");
Date expectedOutputDate = dateFormat.parse("09/10/2018 1:05 PM");
CurrentSessionsResponse fakeResponse = new CurrentSessionsResponse();
CurrentSession fakeSession = new CurrentSession("fake","","");
fakeSession.zone = "Fake 1";
ArrayList<CurrentSession> sessionlist = new ArrayList<>();
sessionlist.add(fakeSession);
fakeResponse.parkingSessions = sessionlist;
MockRepository mockRepo = mock(MockRepository.class);
doReturn(fakeResponse).when(mockRepo).getCurrentSessions();
ExtendedVariableRateUtil variableRateUtil = new ExtendedVariableRateUtil();
variableRateUtil.init(testZone,"", 360, mockRepo);
variableRateUtil.setTime(5);
assertThat(variableRateUtil.getDidJump(), is(false));
assertThat(variableRateUtil.getCurrentCustomerTime(), is(expectedOutputDate));
}
Here is the relevant method in the class that is under test:
fun init(zone: ParkingMeter, plateNumber: String, extendedVariableRateMaxDayMinutes: Int, repository: ParkSmarterRepository) {
this.selectedZone = zone
this.plateNumber = plateNumber
this.extendedVariableRateMaxDayMinutes = extendedVariableRateMaxDayMinutes
if (selectedZone.maxTime < extendedVariableRateMaxDayMinutes * 24 * 60) {
//extendedVariableRateMaxDayJump = selectedZone.maxTime!
}
repository.getCurrentSessions { list, message ->
if (list?.size!! > 0) {
list.forEach {
if (it.zoneID == selectedZone.ZoneId && it.vehicle == plateNumber) {
isNewSession = false
var startDate = it.startTime
var endDate = it.endTime
} else {
isNewSession = true
}
}
}
setCurrentCustomerTime()
}
}
The callback function from retrofit:
fun getCurrentSessions(callback: (currentSessions: List<CurrentSession>?, message: String?) -> Unit) {
val call = ParkSmarter.apiInterface.getCurrentSessions()
call.enqueue(object : Callback<CurrentSessionsResponse> {
override fun onResponse(call: Call<CurrentSessionsResponse>, responseParkSmarter: Response<CurrentSessionsResponse>?) {
try {
callback(responseParkSmarter?.body()?.parkingSessions, responseParkSmarter?.body()?.psResponse?.Message)
} catch (ex: Exception) {
ex.printStackTrace()
}
}
override fun onFailure(call: Call<CurrentSessionsResponse>, t: Throwable?) {
callback(null, "Network Error")
}
})
}
and the retrofit call
#Headers(HEADERS)
#GET("/api/ParkingSession")
Call<CurrentSessionsResponse> getCurrentSessions();

Categories

Resources