Either the api for `MapProperty` is confusing, or ...
# plugin-development
n
Either the api for
MapProperty
is confusing, or I'm doing something wrong. There seems to be a difference in behavior between using
putAll
vs individual
put
invocations when setting lazy
Provider
instances as values for these keys. Details in thread ๐Ÿงต
If I initialize a few entries in a
MapProperty
using
putAll
, it seems I can't provide entry values with
Providers
. e.g.
Copy code
myExtension.mapProp.putAll(mapOf(
    "key1" to providers.provider { "myLazyStringValue" },
    "key2" to providers.provider { "myOtherLazyStringValue" },
))
When reading from this map, I get back an instance of
org.gradle.api.internal.provider.TransformBackedProvider
instead of the expected `String`:
Copy code
assertInstanceOf<String>(myExtension.mapProp.get()["key1"])
results in
Copy code
Unexpected type, expected: <java.lang.String> but was: <org.gradle.api.internal.provider.TransformBackedProvider>
Expected :class java.lang.String
Actual   :class org.gradle.api.internal.provider.TransformBackedProvider
Yet individual
put
statements do work. e.g.
Copy code
myExtension.mapProp.run {
    put("key1", providers.provider { "myLazyStringValue" }
    put("key2", providers.provider { "myOtherLazyStringValue" }
}
When reading from this map, I get back the expected
String
and the assertion passes. Are my expectations for
putAll
wrong? If so could this be made explicit in the documentation/javadoc?
m
Is your
MapProperty
a
MapProperty<String, Object>
? Otherwise the
putAll
shouldn't compile.
๐Ÿ‘† 1
m
I'd love it if
Property<Map>
were allowed. Most of the time, I don't need per-collector laziness and the mental model would be a lot simpler.
n
It is indeed
@get:Input val mapProp: MapProperty<String, Any>
technically the values could be anything, they don't necessarily need to be of type
String
m
This is the intended behavior. In general, for the collection properties Gradle has two kinds of "inserters": for values (accepting value type, like
String
), and for lazy values (accepting providers of value types, like
Provider<String>
). There are also "batch" versions, accepting either collection of values (
Iterable<String>
) or lazy collection of values (
Provider<Iterable<String>>
). There is no version that accepts a collection of lazy values, because these can be trivially expressed with the lazy value inserter, like:
mapOfProviders.forEach(mapProperty::put)
(and generic erasure gets in the way if we want to provide an overload). The type signatures of the inserter methods already make the contract quite obvious, IMO. Unfortunately, with
Any
you don't have the type restriction to help.
n
I understand now that I jumped to the wrong conclusion by assuming that the presence of the
put(K key, Provider<V> providerOfValue)
would "play nice" with
putAll
. But given that I looked through the javadoc multiple times while implementing this and I still ended up doing it wrong, indicates to me that it's not very intuitive. Perhaps an
@apiNote
in the javadoc for
putAll
that warns about
V == Any/Object
and the pitfall of adding value-providers in that case? I do understand it's a corner-case and the api is mostly clear (thank you for providing the extra context), but it still leads to a confusing situation.
a
MapProperty is confusing for a few other reasons too! I think in your case you could consider using NamedDomainObjectContainer, or even ExtensiblePolymorphicDomainObjectContainer instead.
๐Ÿ’ฏ 1