1

Based on the following Kotlin code (from Glance AppWidget), how can I inject the fileKey in the dataStore() method (which seems possible due to the comment above) ?

 /**
 * Use the same file name regardless of the widget instance to share data between them
 *
 * If you need different state/data for each instance, create a store using the provided fileKey
 */
private val Context.datastore by dataStore(DATA_STORE_FILENAME, WeatherInfoSerializer)

override suspend fun getDataStore(
    context: Context,
    fileKey: String
): DataStore<SingleWidgetInfo> {
    return context.datastore // How to use fileKey while creating the datastore ?
}

What I tried failed :

enter image description here

enter image description here

Thanks

Jscti
  • 14,096
  • 4
  • 62
  • 87

3 Answers3

1

Got it working, but it's not very pretty. Code underneath dateStore was not public, So I had to copy/paste/adapt a bit of code :

object SingleWidgetDataStateDefinition : GlanceStateDefinition<SingleWidgetData> {

    private const val DATA_STORE_FILENAME = "singleWidget"

    //private val Context.datastore by dataStore(DATA_STORE_FILENAME, SingleWidgetDataSerializer)

    override suspend fun getDataStore(
        context: Context,
        fileKey: String
    ): DataStore<SingleWidgetData> {
        return DataStoreSingletonDelegate(
            serializer = SingleWidgetDataSerializer,
            fileName = DATA_STORE_FILENAME + fileKey,
            corruptionHandler = null,
            produceMigrations = { listOf() },
            scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
        ).getValue(context)
    }
}

and

class DataStoreSingletonDelegate<T> internal constructor(
    private val fileName: String,
    private val serializer: Serializer<T>,
    private val corruptionHandler: ReplaceFileCorruptionHandler<T>?,
    private val produceMigrations: (Context) -> List<DataMigration<T>>,
    private val scope: CoroutineScope
) {

    private val lock = Any()

    @GuardedBy("lock")
    @Volatile
    private var INSTANCE: DataStore<T>? = null

    fun getValue(thisRef: Context): DataStore<T> {
        return INSTANCE ?: synchronized(lock) {
            if (INSTANCE == null) {
                val applicationContext = thisRef.applicationContext
                INSTANCE = DataStoreFactory.create(
                    serializer = serializer,
                    produceFile = { applicationContext.dataStoreFile(fileName) },
                    corruptionHandler = corruptionHandler,
                    migrations = produceMigrations(applicationContext),
                    scope = scope
                )
            }
            INSTANCE!!
        }
    }
}
Jscti
  • 14,096
  • 4
  • 62
  • 87
1

I was able to get help from the Kotlin slack group which has a Glance channel. The change below is quite simple and is able to be added directly to the example WeatherApp and only changes a line or two in the worker (so that each widget displays different information for testing purposes).

// Rename DATA_STORE_FILENAME for clarity
 private const val DATA_STORE_FILENAME_PREFIX = "weatherInfo_"

// The below line can be removed as it is no longer needed
//private val Context.datastore by dataStore(DATA_STORE_FILENAME, WeatherInfoSerializer)


// getDataStore now uses DataStoreFactory
    override suspend fun getDataStore(context: Context, fileKey: String) = DataStoreFactory.create(
        serializer = WeatherInfoSerializer,
        produceFile = { getLocation(context, fileKey) }
    )

    override fun getLocation(context: Context, fileKey: String) =
        context.dataStoreFile(DATA_STORE_FILENAME_PREFIX + fileKey.lowercase())
ddxv
  • 23
  • 5
0

the dataStore function doesn't return a DataStore itself but is meant to be used together with the by keyword. I'm not entirely sure but I believe this might work:

override suspend fun getDataStore(
    context: Context,
    fileKey: String
): DataStore<SingleWidgetInfo> {
    val store by dataStore(DATA_STORE_FILENAME + fileKey, WeatherInfoSerializer)
    return store
}
Ivo
  • 18,659
  • 2
  • 23
  • 35
  • Thanks for your reply but I also tried that and got the following (not very explicit) error : "Property delegate must have a 'getValue(Nothing?, KProperty*>)' method. None of the following functions are suitable. getValue(Context, KProperty<*>) defined in kotlin.properties.ReadOnlyProperty" (updated question) – Jscti Jun 27 '23 at 12:14
  • ah okay, I have no idea then – Ivo Jun 27 '23 at 12:33