Last extension function is not called - android

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.

Related

Android - HealthConnectClient.readRecords or aggregateGroupByPeriod takes forever

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"

Is there a way to add return to a coroutine?

I just want to know if it is possible for me to return activePodcastViewData. I get return not allow here anytime I tried to call it on the activePodcastViewData.Without the GlobalScope I do get everything working fine.However I updated my repository by adding suspend method to it.Hence I was getting Suspend function should only be called from a coroutine or another suspend function.
fun getPodcast(podcastSummaryViewData: PodcastViewModel.PodcastSummaryViewData): PodcastViewData? {
val repo = podcastRepo ?: return null
val url = podcastSummaryViewData.url ?: return null
GlobalScope.launch {
val podcast = repo.getPodcast(url)
withContext(Dispatchers.Main) {
podcast?.let {
it.feedTitle = podcastViewData.name ?: ""
it.imageUrl = podcastViewData.imageUrl ?: ""
activePodcastViewData = PodcastView(it)
activePodcastViewData
}
}
}
return null
}
class PodcastRepo {
val rssFeedService =RssFeedService.instance
suspend fun getPodcast(url:String):Podcast?{
rssFeedService.getFeed(url)
return Podcast(url,"No name","No Desc","No image")
}
I'm not sure that I understand you correctly but if you want to get activePodcastViewData from coroutine scope you should use some observable data holder. I will show you a simple example with LiveData.
At first, add implementation:
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
Now, in your ViewModel we need to create mutableLiveData to hold and emit our future data.
val podcastsLiveData by lazy { MutableLiveData<Podcast>() }
Here your method: (I wouldn't recommend GlobalScope, let's replace it)
fun getPodcast(podcastSummaryViewData: PodcastViewModel.PodcastSummaryViewData): PodcastViewData? {
val repo = podcastRepo ?: return null
val url = podcastSummaryViewData.url ?: return null
CoroutineScope(Dispatchers.IO).launch {
val podcast = repo.getPodcast(url)
withContext(Dispatchers.Main) {
podcast?.let {
it.feedTitle = podcastViewData.name ?: ""
it.imageUrl = podcastViewData.imageUrl ?: ""
activePodcastViewData = PodcastView(it)
}
}
}
podcastsLiveData.postValue(activePodcastViewData)
}
As you can see your return null is turned to postValue(). Now you finally can observe this from your Activity:
viewModel.podcastsLiveData.observe(this) {
val podcast = it
//Use your data
}
viewModel.getPodcast()
Now every time you call viewModel.getPodcast() method, code in observe will be invoked.
I hope that I helped some :D

Android Joda-Time with Kotlin

I am trying to create a function to check a given string time HH:mm is in range of another comparing to now.
Example: if the current hour is between 12:35 and 15:00 return true
But I always got false even if the current time is in range..
fun isTimeInRange(before: String, after: String): Boolean {
val now = DateTime.now()
val format = DateTimeFormat.forPattern("HH:mm")
return now >= DateTime.parse(before, format) && now <= DateTime.parse(after, format)
}
You need to do:
fun isTimeInRange(before: String, after: String): Boolean {
val now = DateTime.now()
val format = DateTimeFormat.forPattern("HH:mm")
return now.isAfter(DateTime.parse(before, format)) && now.isBefore(DateTime.parse(after, format))
}
Or use intervals.
You have to set before/after date to today's date. try this:
fun isTimeInRange(start: String, end: String): Boolean {
val now = DateTime.now()
val format = DateTimeFormat.forPattern("HH:mm")
val startTime: LocalTime = format.parseLocalTime(start)
val endTime: LocalTime = format.parseLocalTime(end)
val timeZone = DateTimeZone.getDefault()
val today: LocalDate = LocalDate.now(timeZone)
val startMoment: DateTime = today.toLocalDateTime(startTime).toDateTime(timeZone)
val endMoment: DateTime = today.toLocalDateTime(endTime).toDateTime(timeZone)
return now.isAfter(startMoment) && now.isBefore(endMoment)
}

UnitTesting async code rxjava/rxkotlin fails with latch = 1 error

So I have the following test that keeps failing with the following error:
java.lang.AssertionError: No values (latch = 1, values = 0, errors = 0, completions = 0)
val ocrProcessor = mockk<FirebaseFormProcessor>()
val date = listOf(DateTextExtraction())
every { ocrProcessor.scan(any(), any(), any()) } answers {
thirdArg<OcrResultCallback>().invoke(date)
}
viewModel = FormViewModel(ocrProcessor)
viewModel.addImage(bitmap)
viewModel.ocrAlert
.test()
.assertValue {
it == date
}
.addTo(disposeBag)
What this tries to test is the following:
override val ocrAlert: PublishSubject<List<TextExtractionInterface>> = PublishSubject.create()
override fun addImage(bitmap: Bitmap) {
if (files.value.isEmpty())
ocrProcessor.scan(bitmap, extract = textExtractionItems) { ocrResult ->
ocrAlert.onNext(ocrResult)
}
}
I am not quite sure what I am doing wrong here but I think it might have to do with threading problems.
edit:
I changed the code to this now:
val toBeTested = viewModel.ocrAlert
.subscribeOn(scheduler)
.observeOn(scheduler)
.test()
viewModel.addImage(bitmap)
toBeTested
.assertValue {
it == date
}
.addTo(disposeBag)

How convert timestamp in Kotlin

l am try to convert timeestamp coming from data json url
TimeFlight.text = list[position].TimeFlight.getDateTime(toString())
l am use list view in my app
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val view : View = LayoutInflater.from(context).inflate(R.layout.row_layout,parent,false)
val TimeFlight = view.findViewById(R.id.time_id) as AppCompatTextView
val LogoAriline = view.findViewById(R.id.logo_image) as ImageView
status.text= list[position].Stauts
TimeFlight.text = list[position].TimeFlight.getDateTime(toString())
Picasso.get().load(Uri.parse("https://www.xxxxxxxxx.com/static/images/data/operators/"+status.text.toString()+"_logo0.png"))
.into(LogoAriline)
return view as View
}
private fun getDateTime(s: String): String? {
try {
val sdf = SimpleDateFormat("MM/dd/yyyy")
val netDate = Date(Long.parseLong(s))
return sdf.format(netDate)
} catch (e: Exception) {
return e.toString()
}
}
Data class for json
data class FlightShdu (val Airline : String ,val TimeFlight : String)
l used that code getDateTime but the format unknown
Assuming TimeFlight is a stringified epoch timestamp (in milliseconds), you should pass that to your getDateTime() function:
TimeFlight.text = getDateTime(list[position].TimeFlight)
(if they are not millis but seconds, then simply multiply them by 1000 before passing them to the Date constructor)
On a side note, depending on the exact use case, creating a new SimpleDateFormat object might not be necessary on every getDateTime() call, you can make it an instance variable.
Also, i'd advise you to take a look at (and follow) the Java naming conventions for both Java and Kotlin applications.
The problem here is that the Date constructor take long as the milliseconds count since 1/1/1970 and the number you are getting is the seconds count.
my suggestion is the following code( you can change the formate):
const val DayInMilliSec = 86400000
private fun getDateTime(s: String): String? {
return try {
val sdf = SimpleDateFormat("MM/dd/yyyy")
val netDate = Date(s.toLong() * 1000 ).addDays(1)
sdf.format(netDate)
} catch (e: Exception) {
e.toString()
}
}
fun Date.addDays(numberOfDaysToAdd: Int): Date{
return Date(this.time + numberOfDaysToAdd * DayInMilliSec)
}
private fun getDateTime(s: String): String? {
return try {
val date = SimpleDateFormat("MM/dd/yyyy HH:mm:ss").format(Date(s.toLong()*1000))
// current timestamp in sec
val epoch = System.currentTimeMillis()/1000
// Difference between two epoc
val dif = epoch - s.toLong()
val timeDif: String
when {
dif<60 -> {
timeDif = "$dif sec ago"
}
dif/60 < 60 -> {
timeDif = "${dif/60} min ago"
}
dif/3600 < 24 -> {
timeDif = "${dif/3600} hour ago"
}
dif/86400 < 360 -> {
timeDif = "${dif/86400} day ago"
}
else ->{
timeDif = "${dif/31556926} year ago"
}
}
"($timeDif) $date"
} catch (e: Exception) {
e.toString()
}
}

Categories

Resources