I am doing something that I thought should be straightforward: A WearOS tile shall display the data fetched via https request. I took the goals tile example from the documentation and added a callback for fetching the data in my Tile Service:
private val repoRetriever = EnergyRetriever()
private val callback = object : Callback<EnergyResult> {
override fun onFailure(call: Call<EnergyResult>?, t:Throwable?) {
Log.e("MainActivity", "Problem calling Energy API {${t?.message}}")
}
override fun onResponse(call: Call<EnergyResult>?, response: Response<EnergyResult>?) {
response?.isSuccessful.let {
val energyResult = response?.body()
Log.i("MainActivity", "SUCCESS! " + energyResult?.time + " - "+energyResult?.consumption + "kW")
EnergyRepository.setEnergy(energyResult);
getUpdater(getApplicationContext()).requestUpdate(GoalsTileService::class.java)
}
}
}
On the top of onTileRequest I initiate the request. I know that the tile will be rendered with the initial/old dataset, which is ok. I just want the tile to update once the data has been fetched:
override fun onTileRequest(requestParams: TileRequest) = serviceScope.future {
if (isNetworkConnected()) {
repoRetriever.getEnergyUpdate(callback)
}
// Retrieves data to populate the Tile.
val energyResult = EnergyRepository.getEnergy()
// Retrieves device parameters to later retrieve font styles for any text in the Tile.
val deviceParams = requestParams.deviceParameters!!
// Creates Tile.
Tile.builder()
// If there are any graphics/images defined in the Tile's layout, the system will
// retrieve them via onResourcesRequest() and match them with this version number.
.setResourcesVersion(RESOURCES_VERSION)
.setFreshnessIntervalMillis(5 * 60 * 1000)
// Creates a timeline to hold one or more tile entries for a specific time periods.
.setTimeline(
Timeline.builder().addTimelineEntry(
TimelineEntry.builder().setLayout(
Layout.builder().setRoot(
// Creates the root [Box] [LayoutElement]
layout(energyResult!!, deviceParams)
)
)
)
).build()
}
This is obviously not working right because the onTileRequest will finish before the HTTP request is done. I also understand that one shouldn't block this function to wait. RequestUpdate() causes problems because the tile won't update again within a 20 second period due to limitations imposed by Google. I've read that one can use Futures in onTileRequest to defer the actual update until the http request returns - however I haven't been able to figure out just how and for the life of me I can't find an understandable example that would apply to what I'm trying to do. I don't even know if using callbacks for the http request is advisable when using futures here.
Anyone got suggestions?
Starting this work from an incoming tile request is problematic for two reasons.
The first tile request, likely stops you sending updates within the next twenty seconds. Ideally you should separate the data refresh from when you happen to get called for a tile. It also looks like your logic is effectively an infinite loop.
Your app might be started temporarily for the tile request, and then return immediately, causing your tileservice to be unbound and your app to shutdown before your update completes.
Instead consider scheduling some background task with WorkManager, load your data, store is in Androidx DataStore or Room as a cache. Trigger the Tile update from Work Manager, and then load your data from the tile request out of that cache.
I don't have a clear example, but I do this in my app and it works well.
https://github.com/yschimke/rememberwear/blob/main/wear/src/main/kotlin/com/google/wear/soyted/app/work/DataRefreshWorker.kt
https://github.com/yschimke/rememberwear/blob/main/wear/src/main/kotlin/com/google/wear/soyted/tile/RememberWearTileProviderService.kt
Related
I'm using Android Kotlin with the SDK 30 and Coroutine 1.4.1.
I have a function that handles incoming messages to display them on my app in a form of temperature measurement. I use CoroutineScope to process the data and save it in the database. These messages are received from a socket.io connection. The problem is that the messages are not displayed in the correct order when a bulk of data is flowing in.
Now I've looked at my nodejs logs and these messages are sent in the correct order so it can't be that.
I'm using a standard Coroutine function.
See below.
fun receiveTmps(data){
CoroutineScope(IO).launch {
val usersJob = launch {
usersBg(data)
}
}
}
Now I know that with Coroutine I can add a join to wait for the job to finish before starting the next one. But because the messages do not come in at once, but flow continuously over a period of 5 to 20 seconds, it is possible that one message is completed faster than the older one. This causes incorrect order.
My question is, is there any way to handle these tasks 1 by 1 while adding multiple jobs to the list?
Any suggestion or idea is appreciated.
Thank you in advance.
UPDATED:
From what I read from the documentation you should also cancel the channel after its done. So that's going to be tricky to close the channel when messages are flowing in and since I don't have a clear number of what's flowing in I'm having a hard time defining that to the channel. I have tested several ways but most of the examples doesnt work or are outdated.
This is the most basic working example but it always has a defined repeat.
val channel = Channel<String>(UNLIMITED)
fun receiveTmps(data:String){
CoroutineScope(Dispatchers.Default).launch {
channel.send(data)
}
}
#ExperimentalCoroutinesApi
fun main() = runBlocking<Unit> {
launch {
// while(!channel.isClosedForReceive){
// val x = channel.receive()
// Log.d("deb", "Temperature.. "+ x)
// }
repeat(3) {
val x = channel.receive()
Log.d("deb", "Temperature.. "+ x)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
receiveTmps("10")
receiveTmps("30")
// Many more...
main()
}
If we need to process some events sequentially then typical solution is to create a queue of events and start a single consumer to process them. In the case of coroutines we can use Channel as a queue and launch a coroutine running in a loop that will be our consumer.
I'm not very familiar with Android, so I may miss something, but it should be something along lines:
fun receiveTmps(data:String){
channel.trySend(data).getOrThrow()
}
fun main() {
lifecycleScope.launch(Dispatchers.Default) {
for (tmp in channel) {
...
}
}
}
My assumption is that you want to stop processing events when the activity/service will be destroyed, ignoring all temps that are still waiting in the queue.
I'm working on an Android app with a constant repeating background process.
From the moment the device starts it should load data off a webpage every minute. It uses XmlPullParser and a simple URL inputstream. It is but 10kb so it isn't that intensive. I believe this kind of task is called Deferred. The information loaded by the process has to be accessible to the Activity once that the user opens the app. The background process also needs to be abled to place a notification once the data shows certain results.
There seem to be multiple methods to achieve this in Android, eg. a JobScheduler, WorkManager or AlarmManager however everything I've tried so far seems to either stop once the activity closes or doesn't run at all. The timing, every minute, also seems to be an issue as for both a repeating job and worker the minimum interval is 15. This one minute doesn't have to be exact. I imagine instead of having a repeating process loading the data once it might be better to have a long running process sleeping for 1m in between loading the data.
I do not have access to the server the application is connecting to. so I can't do a FirebaseMessagingService.
What would be the best way to schedule such a background process?
How can the activity best exchange information with that process?
I'm open for all suggestions,
thank you for your time.
Easy with WorkManager, it's the most encouraged way for Scheduling Repeating background work in Android, see introduction.
As you say, the minimum repeating work request interval is restricted to 15 minutes, the only way to break it is to Repeatedly schedule the one-time work.
1. Setup Your Worker Class:
class ToastShower(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
withContext(Dispatchers.Main) { //ui related work must run in Main thread!!
Toast.makeText(applicationContext, "Hey, I'm Sam! This message will appear every 5 seconds.", Toast.LENGTH_SHORT).show()
}
return Result.success()
}
}
2. Setup Your Custom Application Class:
class WorkManagerApplication : Application() {
private val backgroundScope = CoroutineScope(Dispatchers.Default) //standard background thread
private val applicationContext = this
override fun onCreate() { //called when the app launches (same as Activity)
super.onCreate()
initWork()
}
private fun initWork() {
backgroundScope.launch { //all rnu in background thread
setupToastShowingWork(0) //no delay at first time
observeToastShowingWork() //observe work state changes, see below
}
}
private fun setupToastShowingWork(delayInSeconds: Long) { //must run in background thread
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) //when using WiFi
.build()
val oneTimeRequest = OneTimeWorkRequestBuilder<ToastShower>() //【for breaking 15 minutes limit we have to use one time request】
.setInitialDelay(delayInSeconds, TimeUnit.SECONDS) //customizable delay (interval) time
.setConstraints(constraints)
.build()
WorkManager.getInstance(applicationContext).enqueueUniqueWork( //【must be unique!!】
ToastShower::class.java.simpleName, //work name, use class name for convenient
ExistingWorkPolicy.KEEP, //if new work comes in with same name, discard the new one
oneTimeRequest
)
}
private suspend fun observeToastShowingWork() {
withContext(Dispatchers.Main) { //must run in Main thread for using observeForever
WorkManager.getInstance(applicationContext).getWorkInfosForUniqueWorkLiveData(ToastShower::class.java.simpleName).observeForever {
if (it[0].state == WorkInfo.State.SUCCEEDED) { //when the work is done
backgroundScope.launch { //prevent from running in Main thread
setupToastShowingWork(5) //every 5 seconds
}
}
}
}
}
}
3. Setup AndroidManifest File:
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.workmanagertest">
<application
android:name=".WorkManagerApplication" //【here, must!!!】
...
</application>
</manifest>
By setting up with above, the work (showing Toast in my example) will be executed (or more clearly, schedule and execute) every 5 seconds no matter the app is in foreground or background or killed by system. Only way to stop it is either uninstall or go inside the app's setting to force-close it.
Demo: https://youtu.be/7IsQQppKqFs
I'm using CameraX's image analysis use case that keeps calling the analyze() method in my custom Analyzer class. Inside analyze(), before doing anything else, I need to send a request to a connected device and wait for its response; the latency is very low and I'm already doing it synchronously with no issues, but I was told it's better to make it asynchronous just in case the device responds too slowly.
I know that MLKit's process() returns a Task<List<T>> and I already call onSuccessListener { } on it, so I was wondering if I can use a similar approach (I can't return a Task<T> from my function, how do I create one?). Otherwise would you suggest threads, or coroutines, or something else?
Edit: below there's a simplified example of what I'm trying to do. For a given frame sent by the camera I just need to perform only the current analysis in line, then I return so that analyze() will be called again with the next frame, on which it will perform the next analysis.
It might look hacky but it's for an app that continuously runs in foreground on a single-purpose device (let's call it Dev A) with no user interaction provided by touch or other conventional means, so it needs some kind of trigger to start doing what is required.
The trigger might as well be when the first image analysis in line is successful, but running MLKit or TFLite models from real time camera feed all day long makes Dev A overheat excessively. The best solution so far seems to be waiting for the trigger to come from an external device (Dev B) that operates independently.
Since Dev B may respond with some delay I need to communicate with it asynchronously, hence the reason for the question in the first place. While there are certainly several architectural nuances to discuss, the current root of the problem is that I can't decide (or rather I don't know) how to handle the repeating "connection" with Dev B in a non-blocking way.
I mean, can I just treat this issue like any other case where multithreading is needed, or the fact that the camera is involved might pose additional threats? The backpressure strategy is set to STRATEGY_KEEP_ONLY_LATEST, so in theory if the current call to analyze() hasn't finished yet the new frames are dropped and nothing bad happens even if inside the method I'm still waiting for the async call to Dev B to finish, or am I missing something?
var connected = false
lateinit var result: Boolean
var analysis1 = true
var analysis2 = true
override fun analyze() {
if (!connected) {
result = connectToDevice() // needs to be async
connected = true
}
// need positive result to proceed, otherwise start over
if (!result) {
connected = false
return
}
if (analysis1) {
// perform analysis #1...
analysis1 = false
// when an analysis is done, exit early and perform next analysis on next frame
return
}
if (analysis2) {
// perform analysis #2...
analysis2 = false
// same as above
return
}
// when all analyses are done, reset all flags to start over
connected = false
analysis1 = true
analysis2 = true
}
I have got Nearby Messages apparently working fine exchanging messages happily between IOS and Android, but my Android app has multiple Activities. Once I switched to the second Activity the messages stop.
I have stripped out all the code in the Android app except for the line Nearby.getMessagesClient(this).subscribe(listener).
I then have a button to switch to a new instance of the same Activity. This works (messages are received from an IOS App that is just sending messages every 15 seconds) as a first Activity, but then fails (no messages received) once I click on the button and it starts itself.
Note that the onSuccessListner callback is triggered (the onFailureListener isn't). It thinks it is registered, but just doesn't get any messages.
I also did this with 2 copies of Activity with the same code, just to check that it wasn't because it was the same Activity class that it was failing. Still failed.
I took the Google sample App. This still uses the deprecated GooglaApiClient. It still gives the same result though.
I did spot that if the user has to take an action to enable the messages it works (as in the sample where the user has to switch messages on). I therefore tried adding a delay. A delay of 400 milliseconds means on my device that it works. 300 milliseconds and it still fails.
So I seem to have 2 choices. Add a spurious 1 second delay before enabling messages, or convert my App to use Fragments and a single Activity (I tried using the Application context and contrary to some documentation I found it tells me it must be an Activity Context). Neither are satisfactory workarounds, so am hoping that I have done something stupid.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fab.setOnClickListener { view -> // Switch to self.
val intent = Intent(this, MainActivity2::class.java)
startActivity(intent)
}
mMessageListener = object : MessageListener() {
override public fun onFound(message: Message) {
Log.d(TAG, "Found message: " + String(message.content)) //Triggered ok until Activity switch
}
}
}
override public fun onResume() {
super.onResume();
uiScope.launch {// Using coroutines as a simple means of adding a delay. It also fails with no coroutine (and no delay)
delay(300) // This fails - but a value of 400 means that messages are received
Nearby.getMessagesClient(this#MainActivity2).subscribe(mMessageListener)
.addOnSuccessListener {
Log.e(TAG, "Success") // Always triggered
}.addOnFailureListener {
Log.e(TAG, "Failed " + it) // Never triggered
}
}
}
override fun onStop() {
Nearby.getMessagesClient(this).unsubscribe(mMessageListener)
super.onStop();
}
This is the code I am currently using, but as commented above it also fails with the old API, with Java, with no coroutines, etc. The Logcat is identical in the 300 or 400 ms cases.
Do you have any ideas on how to solve in an android long time of waiting on server response?
I am making a request to the API saving profile.
This request is fast
But the server is processing it rather long 10sec to 3min (like scanning cheap flight on some tickets sites)
After the response, I need to redirect the user to a confirmation screen or home screen of the app.
I solved this by setting the longer timeout to 45 sec and then always redirecting to confirmation + alert that it takes longer.
On the home screen, I am displaying "Processing..." label until the server finishes
But this solution has some problems like:
what about user going out by home button if it takes longer, or switching applications, and if just display goes to sleep while untouched more than 30sec? Then activity/fragment is recreated and response seems to not arrive.
I consider adding push notification when processing is done this could help a little. Is there any way to solve such an issue? Maybe some background Service? But isn't Android Services deprecated? I think only Foreground Services are valid to use, or maybe new WorkManagers (but this doesn't seem to fit this scenario). And how from then wakeup screen and move it to the next page.
Code sample:
// Fragment
viewModel.saveData(data)
// View Model
fun saveData(data: Data) : LiveData<Resource<DataResponse>> {
_dataEvent.postValue(Event(Resource.loading(null)))
val apiSource = dataRepo.saveData(data)
_dataEvent.addSource(apiSource) { resource ->
_dataEvent.removeSource(apiSource)
val resource = resource ?: Resource.error(null, null)
_dataEvent.postValue(Event(resource))
}
return apiSource
}
// Observing Data Event
viewModel.dataEvent.observe(this,
Observer { event ->
if(event?.peekContent()?.status == Resource.Status.LOADING) {
showProgressAlert(context)
event.getContentIfNotHandled() // consume loading event
}
val resource = event?.getContentIfNotHandled()
if(resource != null) {
hideProgressAlert()
if (resource.status == Resource.Status.SUCCESS) {
showSuccessAlert(context)
navigateToConfirmPage()
} else if (resource.status == Resource.Status.ERROR) {
if (throwable is SocketTimeoutException) {
showTimeoutAlert(context)
navigateToConfirmPage()
} else {
showErrorAlert(context)
}
}
}
If this request is taking a long time then you should perform it in a background thread, Since android oreo background services became very restricted if you want to implement it you can use JobIntentService or JobScheduler or make it a ForegroundService but in this case you will have to show a notification to the user while the service is running, You can read more about it here https://developer.android.com/about/versions/oreo/background
Another approach is to use RXjava which handles threading and perform background services very smoothly