2

I'm trying to create a custom WearOS complication that shows the title of the next event. The watch has a built-in Agenda app that synchronizes with the phone's calendar. There is also a built-in complication that shows the time of the next event, from the same Agenda app. When I change the next event in my phone (the time and the title), the complication updates immediately (it takes 5-10 seconds) and the new information is also available in the watch's Agenda app. I want my complication to do the same, but it just doesn't update.

So far, I've created a SuspendingComplicationDataSourceService as follows:

class ComingEventComplication : SuspendingComplicationDataSourceService() {
    
    override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? {
        val nextEvent = getNextCalendarEvents(this.contentResolver)[0]

        return if (nextEvent != null) {
            //Expose data to the complication
        } else {
            //Show a placeholder
        }
    }

    //Preview data
    override fun getPreviewData(type: ComplicationType): ComplicationData? {
        [...]
    }
}

I'm not showing the code to expose the data to make it shorter (but it works). The getNextCalendarEvents() function is the following:

fun getNextCalendarEvents(contentResolver: ContentResolver): List<CalendarEvent> {
    val events = mutableListOf<CalendarEvent>()

    // Define the projection (columns you want to retrieve)
    val projection = arrayOf(
        CalendarContract.Instances.EVENT_ID,
        CalendarContract.Instances.TITLE,
        CalendarContract.Instances.DESCRIPTION,
        CalendarContract.Instances.EVENT_LOCATION,
        CalendarContract.Instances.BEGIN,
        CalendarContract.Instances.END,
        CalendarContract.Instances.ALL_DAY
    )

    val startMillis = System.currentTimeMillis()
    val endMillis = startMillis + TimeUnit.DAYS.toMillis(5)

    val uriBuilder = WearableCalendarContract.Instances.CONTENT_URI.buildUpon()
    ContentUris.appendId(uriBuilder, startMillis)
    ContentUris.appendId(uriBuilder, endMillis)
    val uri = uriBuilder.build()

    // Query the Content Provider
    val cursor = contentResolver.query(
        uri,
        projection,
        null,
        null,
        null
    )

    cursor?.use {
        while (it.moveToNext()) {
            val eventId = it.getLong(it.getColumnIndexOrThrow(CalendarContract.Instances.EVENT_ID))
            val title = it.getString(it.getColumnIndexOrThrow(CalendarContract.Instances.TITLE))
            val description =
                it.getString(it.getColumnIndexOrThrow(CalendarContract.Instances.DESCRIPTION))
            val location =
                it.getString(it.getColumnIndexOrThrow(CalendarContract.Instances.EVENT_LOCATION))
            val beginTime = it.getLong(it.getColumnIndexOrThrow(CalendarContract.Instances.BEGIN))
            val endTime = it.getLong(it.getColumnIndexOrThrow(CalendarContract.Instances.END))
            val isAllDay =
                it.getInt(it.getColumnIndexOrThrow(CalendarContract.Instances.ALL_DAY)) == 1

            events.add(
                CalendarEvent(
                    id = eventId,
                    title = title,
                    description = description,
                    location = location,
                    startTime = Instant.ofEpochMilli(beginTime),
                    endTime = Instant.ofEpochMilli(endTime),
                    isAllDay = isAllDay
                )
            )
        }
    }

    return events.sortedBy { it.startTime }
}

with my custom data class:

data class CalendarEvent(
    val title: String,
    val startTime: Instant,
    val endTime: Instant,
    val isAllDay: Boolean,
    val description: String?,
    val location: String?,
    val id: Long
) 

Finally, my AndroidManifest setup is:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission-sdk-23 android:name="android.permission.READ_CALENDAR" />
<uses-feature android:name="android.hardware.type.watch" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@android:style/Theme.DeviceDefault" >
    <meta-data
        android:name="com.google.android.wearable.standalone"
        android:value="true" />
<service
            android:name="developer.pion.complicationsprovider.services.ComingEventComplication"
            android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER"
            android:exported="true"
            android:label="Test"
            android:icon="@drawable/common_full_open_on_phone">
            <intent-filter>
                <action android:name="androidx.wear.watchface.complications.action.COMPLICATION_DATA_SOURCE" />
            </intent-filter>
            <meta-data
                android:name="android.support.wearable.complications.SUPPORTED_TYPES"
                android:value="SHORT_TEXT" />

        </service>
    </application>

This part works as expected: I add the complication and it shows the name of the next event in my calendar. The problem comes when I change the next event in my calendar on my phone. The built-in complication updates but not mine (I have to re-add it again). So far I've tried adding a WorkManager to update the complication (which is not very reliable if you set it to schedule every long time, and it may drain the battery otherwise), and using a ContentObserver :

class ContentObserver(
    private val context: Context,
    private val componentName: ComponentName
) : ContentObserver(Handler(Looper.getMainLooper())) {

    override fun onChange(selfChange: Boolean, uri: Uri?) {
        super.onChange(selfChange, uri)
        Log.d("DEBUG_", "onChange: Changed!")
        requestComplicationUpdate()
    }

    private fun requestComplicationUpdate() {
        val requester = ComplicationDataSourceUpdateRequester.create(
            context = context,
            complicationDataSourceComponent = componentName
        )
        requester.requestUpdateAll()
    }
}

I do register my ContenObserver inside ComingEventComplication class:

override fun onCreate() {
    super.onCreate()
    val calendarUri: Uri = WearableCalendarContract.CONTENT_URI

    // Create the ContentObserver instance.
    calendarObserver = ContentObserver(
        context = applicationContext,
        componentName = ComponentName(applicationContext, ComingEventComplication::class.java)
    )

    // Register the observer.
    contentResolver.registerContentObserver(
        calendarUri,
        true,
        calendarObserver
    )
}

override fun onDestroy() {
        super.onDestroy()
        // Unregister the observer to prevent memory leaks.
        contentResolver.unregisterContentObserver(calendarObserver)
    }

The DEBUG_ onChange: Changed! is never shown in my logcat. I also tried adding a receiver in my manifest:

<receiver android:name=".receivers.CalendarUpdateReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.PROVIDER_CHANGED" />
        <data android:scheme="content" android:host="com.google.android.wearable.provider.calendar" />
    </intent-filter>
</receiver>

setting the android:host as the Uri given by WearableCalendarContract.CONTENT_URI , WearableCalendarContract.Instances.CONTENT_URI and CalendarContract.CONTENT_URI . The receiver calls a dummy BroadcastReceiver that displays a log entry. I had no success with any of them.

Up to now I'm not sure if I'm setting something wrong or if I need a different approach. I've tested third-party complications that are able to update in less than a minute from the event change on phone, so I think there must be a reliable way of doing this. Any help will be appreciated!

1
  • you requester.requestUpdateAll() will work, i use this myself. i don't know about getting calendar events by code. the watchfaces probably use the system data source for that. Commented Oct 31 at 9:32

1 Answer 1

0

As far as I am aware, there are only two ways besides the one you mentioned...

1 - You can set an update frequency for your complication adding this tag to your service



<meta-data

 android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"

 android:value="300" />

2 - You create a companion app for the phone, set a receiver for events changes and send a flag to your watch app to update the complication using the data layer API

<receiver android:name=".receiver.CalendarReceiver" android:exported="true">                  
   <intent-filter>                 
      <action android:name="android.intent.action.PROVIDER_CHANGED"/>                 
      <data android:scheme="content"/>                 
      <data android:host="com.android.calendar"/>             
   </intent-filter>
</receiver>
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for your answer! As my app is designed to be standalone, it has no companion app. Setting an update frequency will keep the data updated, but it won't update the data as soon as it's changed on the calendar. I am looking for a on-change trigger.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.