Merge "Expose convertVisibilityConfigFromProto to public" into androidx-main
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
index 93530a91..5778397 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
+import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
@@ -40,8 +41,11 @@
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
+import androidx.annotation.UiThread;
import androidx.appcompat.R;
+import androidx.collection.LruCache;
import androidx.core.content.res.ResourcesCompat;
+import androidx.core.util.Pair;
import androidx.core.util.TypedValueCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.inputmethod.EditorInfoCompat;
@@ -86,6 +90,14 @@
mAutoSizeTextHelper = new AppCompatTextViewAutoSizeHelper(mView);
}
+ @RequiresApi(26)
+ @UiThread
+ @NonNull
+ static Typeface createVariationInstance(@NonNull Typeface baseTypeface,
+ @NonNull String fontVariationSettings) {
+ return Api26Impl.createVariationInstance(baseTypeface, fontVariationSettings);
+ }
+
@SuppressLint("NewApi")
void loadFromAttributes(@Nullable AttributeSet attrs, int defStyleAttr) {
final Context context = mView.getContext();
@@ -772,6 +784,17 @@
@RequiresApi(26)
static class Api26Impl {
+ /**
+ * Cache for variation instances created based on an existing Typeface
+ */
+ private static final LruCache<Pair<Typeface, String>, Typeface> sVariationsCache =
+ new LruCache<>(30);
+
+ /**
+ * Used to create variation instances; initialized lazily
+ */
+ private static @Nullable Paint sPaint;
+
private Api26Impl() {
// This class is not instantiable.
}
@@ -788,6 +811,54 @@
return textView.setFontVariationSettings(fontVariationSettings);
}
+ /**
+ * Create a new Typeface based on {@code baseTypeFace} with the specified variation
+ * settings. Uses a cache to avoid memory scaling with the number of AppCompatTextViews.
+ *
+ * @param baseTypeface the original typeface, preferably without variations applied
+ * (used both to create the new instance, and as a cache key).
+ * Note: this method will correctly handle instances with variations
+ * applied, as we have no way of detecting that. However, cache hit
+ * rates may be decreased.
+ * @param fontVariationSettings the new font variation settings.
+ * This is used as a cache key without sorting, to avoid
+ * additional per-TextView allocations to parse and sort the
+ * variation settings. App developers should strive to provide
+ * the settings in the same order every time within their app,
+ * in order to get the best cache performance.
+ * @return the new instance, or {@code null} if
+ * {@link Paint#setFontVariationSettings(String)} would return null for this
+ * Typeface and font variation settings string.
+ */
+ @Nullable
+ @UiThread
+ static Typeface createVariationInstance(@Nullable Typeface baseTypeface,
+ @Nullable String fontVariationSettings) {
+ Pair<Typeface, String> cacheKey = new Pair<>(baseTypeface, fontVariationSettings);
+
+ Typeface result = sVariationsCache.get(cacheKey);
+ if (result != null) {
+ return result;
+ }
+ Paint paint = sPaint != null ? sPaint : (sPaint = new Paint());
+
+ // Work around b/353609778
+ if (Objects.equals(paint.getFontVariationSettings(), fontVariationSettings)) {
+ paint.setFontVariationSettings(null);
+ }
+
+ // Use Paint to create a new Typeface based on an existing one
+ paint.setTypeface(baseTypeface);
+ boolean effective = paint.setFontVariationSettings(fontVariationSettings);
+ if (effective) {
+ result = paint.getTypeface();
+ sVariationsCache.put(cacheKey, result);
+ return result;
+ } else {
+ return null;
+ }
+ }
+
static int getAutoSizeStepGranularity(TextView textView) {
return textView.getAutoSizeStepGranularity();
}
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
index 0b7c10b..42f25b5 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
@@ -96,6 +96,20 @@
private boolean mIsSetTypefaceProcessing = false;
+ /**
+ * Equivalent to Typeface.mOriginalTypeface.
+ * Used to correctly emulate the behavior of getTypeface(), because we need to call setTypeface
+ * directly in order to implement caching of variation instances of typefaces.
+ */
+ private Typeface mOriginalTypeface;
+
+ /**
+ * The currently applied font variation settings.
+ * Used to make getFontVariationSettings somewhat more accurate with Typeface instance caching,
+ * as we don't call super.setFontVariationSettings.
+ */
+ private String mFontVariationSettings;
+
@Nullable
private SuperCaller mSuperCaller = null;
@@ -160,7 +174,6 @@
/**
* This should be accessed via
* {@link androidx.core.view.ViewCompat#setBackgroundTintList(android.view.View, ColorStateList)}
- *
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@Override
@@ -173,7 +186,6 @@
/**
* This should be accessed via
* {@link androidx.core.view.ViewCompat#getBackgroundTintList(android.view.View)}
- *
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@Override
@@ -186,7 +198,6 @@
/**
* This should be accessed via
* {@link androidx.core.view.ViewCompat#setBackgroundTintMode(android.view.View, PorterDuff.Mode)}
- *
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@Override
@@ -199,7 +210,6 @@
/**
* This should be accessed via
* {@link androidx.core.view.ViewCompat#getBackgroundTintMode(android.view.View)}
- *
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@Override
@@ -217,6 +227,38 @@
}
}
+ /**
+ * Set font variation settings.
+ * See {@link TextView#setFontVariationSettings(String)} for details.
+ * <p>
+ * <em>Note:</em> Due to performance optimizations,
+ * {@code getPaint().getFontVariationSettings()} will be less reliable than if not using
+ * AppCompatTextView. You should prefer {@link #getFontVariationSettings()}, which will be more
+ * accurate. However, neither approach will work correctly if using Typeface objects with
+ * embedded font variation settings.
+ */
+ @RequiresApi(26)
+ @Override
+ public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
+ Typeface variationTypefaceInstance = AppCompatTextHelper.Api26Impl.createVariationInstance(
+ mOriginalTypeface, fontVariationSettings);
+ if (variationTypefaceInstance != null) {
+ // Call superclass method directly to bypass overwriting mOriginalTypeface
+ super.setTypeface(variationTypefaceInstance);
+ mFontVariationSettings = fontVariationSettings;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Nullable
+ @RequiresApi(26)
+ @Override
+ public String getFontVariationSettings() {
+ return mFontVariationSettings;
+ }
+
@Override
public void setFilters(@SuppressWarnings("ArrayReturn") @NonNull InputFilter[] filters) {
super.setFilters(getEmojiTextViewHelper().getFilters(filters));
@@ -755,6 +797,20 @@
}
@Override
+ public void setTypeface(@Nullable Typeface tf) {
+ mOriginalTypeface = tf;
+ super.setTypeface(tf);
+ }
+
+ @Override
+ @Nullable
+ // Code inspection reveals that the superclass method can return null.
+ @SuppressWarnings("InvalidNullabilityOverride")
+ public Typeface getTypeface() {
+ return mOriginalTypeface;
+ }
+
+ @Override
public void setTypeface(@Nullable Typeface tf, int style) {
if (mIsSetTypefaceProcessing) {
// b/151782655
diff --git a/appsearch/appsearch-builtin-types/api/current.txt b/appsearch/appsearch-builtin-types/api/current.txt
index 3255d7d..7f4b2f3 100644
--- a/appsearch/appsearch-builtin-types/api/current.txt
+++ b/appsearch/appsearch-builtin-types/api/current.txt
@@ -15,18 +15,18 @@
@androidx.appsearch.annotation.Document(name="builtin:Alarm") public class Alarm extends androidx.appsearch.builtintypes.Thing {
method public String? getBlackoutPeriodEndDate();
method public String? getBlackoutPeriodStartDate();
- method public int getComputingDevice();
method public int[]? getDaysOfWeek();
method @IntRange(from=0, to=23) public int getHour();
method @IntRange(from=0, to=59) public int getMinute();
method public androidx.appsearch.builtintypes.AlarmInstance? getNextInstance();
+ method public int getOriginatingDevice();
method public androidx.appsearch.builtintypes.AlarmInstance? getPreviousInstance();
method public String? getRingtone();
method public boolean isEnabled();
method public boolean shouldVibrate();
- field public static final int COMPUTING_DEVICE_SMART_PHONE = 1; // 0x1
- field public static final int COMPUTING_DEVICE_SMART_WATCH = 2; // 0x2
- field public static final int COMPUTING_DEVICE_UNKNOWN = 0; // 0x0
+ field public static final int ORIGINATING_DEVICE_SMART_PHONE = 1; // 0x1
+ field public static final int ORIGINATING_DEVICE_SMART_WATCH = 2; // 0x2
+ field public static final int ORIGINATING_DEVICE_UNKNOWN = 0; // 0x0
}
@androidx.appsearch.annotation.Document.BuilderProducer public static final class Alarm.Builder {
@@ -40,7 +40,6 @@
method public androidx.appsearch.builtintypes.Alarm.Builder setAlternateNames(java.util.List<java.lang.String!>?);
method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodEndDate(String?);
method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodStartDate(String?);
- method public androidx.appsearch.builtintypes.Alarm.Builder setComputingDevice(int);
method public androidx.appsearch.builtintypes.Alarm.Builder setCreationTimestampMillis(long);
method public androidx.appsearch.builtintypes.Alarm.Builder setDaysOfWeek(@IntRange(from=java.util.Calendar.SUNDAY, to=java.util.Calendar.SATURDAY) int...?);
method public androidx.appsearch.builtintypes.Alarm.Builder setDescription(String?);
@@ -52,6 +51,7 @@
method public androidx.appsearch.builtintypes.Alarm.Builder setMinute(@IntRange(from=0, to=59) int);
method public androidx.appsearch.builtintypes.Alarm.Builder setName(String?);
method public androidx.appsearch.builtintypes.Alarm.Builder setNextInstance(androidx.appsearch.builtintypes.AlarmInstance?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setOriginatingDevice(int);
method public androidx.appsearch.builtintypes.Alarm.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
method public androidx.appsearch.builtintypes.Alarm.Builder setPreviousInstance(androidx.appsearch.builtintypes.AlarmInstance?);
method public androidx.appsearch.builtintypes.Alarm.Builder setRingtone(String?);
diff --git a/appsearch/appsearch-builtin-types/api/restricted_current.txt b/appsearch/appsearch-builtin-types/api/restricted_current.txt
index 1fdbab2..32acfbf 100644
--- a/appsearch/appsearch-builtin-types/api/restricted_current.txt
+++ b/appsearch/appsearch-builtin-types/api/restricted_current.txt
@@ -17,18 +17,18 @@
@androidx.appsearch.annotation.Document(name="builtin:Alarm") public class Alarm extends androidx.appsearch.builtintypes.Thing {
method public String? getBlackoutPeriodEndDate();
method public String? getBlackoutPeriodStartDate();
- method public int getComputingDevice();
method public int[]? getDaysOfWeek();
method @IntRange(from=0, to=23) public int getHour();
method @IntRange(from=0, to=59) public int getMinute();
method public androidx.appsearch.builtintypes.AlarmInstance? getNextInstance();
+ method public int getOriginatingDevice();
method public androidx.appsearch.builtintypes.AlarmInstance? getPreviousInstance();
method public String? getRingtone();
method public boolean isEnabled();
method public boolean shouldVibrate();
- field public static final int COMPUTING_DEVICE_SMART_PHONE = 1; // 0x1
- field public static final int COMPUTING_DEVICE_SMART_WATCH = 2; // 0x2
- field public static final int COMPUTING_DEVICE_UNKNOWN = 0; // 0x0
+ field public static final int ORIGINATING_DEVICE_SMART_PHONE = 1; // 0x1
+ field public static final int ORIGINATING_DEVICE_SMART_WATCH = 2; // 0x2
+ field public static final int ORIGINATING_DEVICE_UNKNOWN = 0; // 0x0
}
@androidx.appsearch.annotation.Document.BuilderProducer public static final class Alarm.Builder {
@@ -42,7 +42,6 @@
method public androidx.appsearch.builtintypes.Alarm.Builder setAlternateNames(java.util.List<java.lang.String!>?);
method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodEndDate(String?);
method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodStartDate(String?);
- method public androidx.appsearch.builtintypes.Alarm.Builder setComputingDevice(int);
method public androidx.appsearch.builtintypes.Alarm.Builder setCreationTimestampMillis(long);
method public androidx.appsearch.builtintypes.Alarm.Builder setDaysOfWeek(@IntRange(from=java.util.Calendar.SUNDAY, to=java.util.Calendar.SATURDAY) int...?);
method public androidx.appsearch.builtintypes.Alarm.Builder setDescription(String?);
@@ -54,6 +53,7 @@
method public androidx.appsearch.builtintypes.Alarm.Builder setMinute(@IntRange(from=0, to=59) int);
method public androidx.appsearch.builtintypes.Alarm.Builder setName(String?);
method public androidx.appsearch.builtintypes.Alarm.Builder setNextInstance(androidx.appsearch.builtintypes.AlarmInstance?);
+ method public androidx.appsearch.builtintypes.Alarm.Builder setOriginatingDevice(int);
method public androidx.appsearch.builtintypes.Alarm.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
method public androidx.appsearch.builtintypes.Alarm.Builder setPreviousInstance(androidx.appsearch.builtintypes.AlarmInstance?);
method public androidx.appsearch.builtintypes.Alarm.Builder setRingtone(String?);
diff --git a/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/builtintypes/AlarmTest.java b/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/builtintypes/AlarmTest.java
index 23a2776..975c794 100644
--- a/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/builtintypes/AlarmTest.java
+++ b/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/builtintypes/AlarmTest.java
@@ -57,7 +57,7 @@
.setShouldVibrate(true)
.setPreviousInstance(alarmInstance1)
.setNextInstance(alarmInstance2)
- .setComputingDevice(Alarm.COMPUTING_DEVICE_SMART_WATCH)
+ .setOriginatingDevice(Alarm.ORIGINATING_DEVICE_SMART_WATCH)
.build();
assertThat(alarm.getNamespace()).isEqualTo("namespace");
@@ -83,7 +83,7 @@
assertThat(alarm.shouldVibrate()).isTrue();
assertThat(alarm.getPreviousInstance()).isEqualTo(alarmInstance1);
assertThat(alarm.getNextInstance()).isEqualTo(alarmInstance2);
- assertThat(alarm.getComputingDevice()).isEqualTo(Alarm.COMPUTING_DEVICE_SMART_WATCH);
+ assertThat(alarm.getOriginatingDevice()).isEqualTo(Alarm.ORIGINATING_DEVICE_SMART_WATCH);
}
@Test
@@ -115,7 +115,7 @@
.setShouldVibrate(true)
.setPreviousInstance(alarmInstance1)
.setNextInstance(alarmInstance2)
- .setComputingDevice(Alarm.COMPUTING_DEVICE_SMART_WATCH)
+ .setOriginatingDevice(Alarm.ORIGINATING_DEVICE_SMART_WATCH)
.build();
Alarm alarm2 = new Alarm.Builder(alarm1).build();
@@ -143,7 +143,7 @@
assertThat(alarm1.shouldVibrate()).isEqualTo(alarm2.shouldVibrate());
assertThat(alarm1.getPreviousInstance()).isEqualTo(alarm2.getPreviousInstance());
assertThat(alarm1.getNextInstance()).isEqualTo(alarm2.getNextInstance());
- assertThat(alarm1.getComputingDevice()).isEqualTo(alarm2.getComputingDevice());
+ assertThat(alarm1.getOriginatingDevice()).isEqualTo(alarm2.getOriginatingDevice());
}
@Test
@@ -177,7 +177,7 @@
.setShouldVibrate(true)
.setPreviousInstance(alarmInstance1)
.setNextInstance(alarmInstance2)
- .setComputingDevice(Alarm.COMPUTING_DEVICE_SMART_WATCH)
+ .setOriginatingDevice(Alarm.ORIGINATING_DEVICE_SMART_WATCH)
.build();
GenericDocument genericDocument = GenericDocument.fromDocumentClass(alarm);
@@ -213,7 +213,7 @@
assertThat(genericDocument.getPropertyDocument("nextInstance"))
.isEqualTo(GenericDocument.fromDocumentClass(alarmInstance2));
assertThat(genericDocument.getPropertyLong("computingDevice"))
- .isEqualTo(Alarm.COMPUTING_DEVICE_SMART_WATCH);
+ .isEqualTo(Alarm.ORIGINATING_DEVICE_SMART_WATCH);
// Test that toDocumentClass doesn't lose information.
GenericDocument newGenericDocument = GenericDocument.fromDocumentClass(
@@ -256,4 +256,14 @@
alarmGenericDocument.toDocumentClass(Alarm.class));
assertThat(newGenericDocument).isEqualTo(alarmGenericDocument);
}
+
+ @Test
+ public void testRenameComputingDevice_rename() throws Exception {
+ GenericDocument genericAlarm =
+ new GenericDocument.Builder<>("namespace1", "id1", "builtin:Alarm")
+ .setPropertyLong("computingDevice", 42)
+ .build();
+ Alarm alarm = genericAlarm.toDocumentClass(Alarm.class);
+ assertThat(alarm.getOriginatingDevice()).isEqualTo(42);
+ }
}
diff --git a/appsearch/appsearch-builtin-types/src/main/java/androidx/appsearch/builtintypes/Alarm.java b/appsearch/appsearch-builtin-types/src/main/java/androidx/appsearch/builtintypes/Alarm.java
index 43fb786..b488d66 100644
--- a/appsearch/appsearch-builtin-types/src/main/java/androidx/appsearch/builtintypes/Alarm.java
+++ b/appsearch/appsearch-builtin-types/src/main/java/androidx/appsearch/builtintypes/Alarm.java
@@ -37,15 +37,18 @@
public class Alarm extends Thing {
/** The device that this {@link Alarm} belongs to. */
@RestrictTo(RestrictTo.Scope.LIBRARY)
- @IntDef({COMPUTING_DEVICE_UNKNOWN, COMPUTING_DEVICE_SMART_PHONE, COMPUTING_DEVICE_SMART_WATCH})
+ @IntDef({
+ ORIGINATING_DEVICE_UNKNOWN,
+ ORIGINATING_DEVICE_SMART_PHONE,
+ ORIGINATING_DEVICE_SMART_WATCH})
@Retention(RetentionPolicy.SOURCE)
- public @interface ComputingDevice {}
+ public @interface OriginatingDevice {}
/** The {@link Alarm} belongs to an unknown device. */
- public static final int COMPUTING_DEVICE_UNKNOWN = 0;
+ public static final int ORIGINATING_DEVICE_UNKNOWN = 0;
/** The {@link Alarm} belongs to a smart phone device. */
- public static final int COMPUTING_DEVICE_SMART_PHONE = 1;
+ public static final int ORIGINATING_DEVICE_SMART_PHONE = 1;
/** The {@link Alarm} belongs to a smart watch device. */
- public static final int COMPUTING_DEVICE_SMART_WATCH = 2;
+ public static final int ORIGINATING_DEVICE_SMART_WATCH = 2;
@Document.BooleanProperty
private final boolean mEnabled;
@@ -77,8 +80,13 @@
@Document.DocumentProperty
private final AlarmInstance mNextInstance;
- @Document.LongProperty
- private final int mComputingDevice;
+ // This property was originally released as computingDevice, and the old name is maintained for
+ // compatibility with existing documents. Since the field is not indexed, the impact is limited
+ // to use of this field as a property path for projections and inheritance.
+ // If this limitation causes problems, we should add the mComputingDevice field back, mark it
+ // deprecated, leave it in the API surface, and provide a migrator for convenience of upgrading.
+ @Document.LongProperty(name = "computingDevice")
+ private final int mOriginatingDevice;
Alarm(@NonNull String namespace, @NonNull String id, int documentScore,
long creationTimestampMillis, long documentTtlMillis, @Nullable String name,
@@ -89,7 +97,7 @@
@Nullable String blackoutPeriodStartDate, @Nullable String blackoutPeriodEndDate,
@Nullable String ringtone, boolean shouldVibrate,
@Nullable AlarmInstance previousInstance, @Nullable AlarmInstance nextInstance,
- int computingDevice) {
+ int originatingDevice) {
super(namespace, id, documentScore, creationTimestampMillis, documentTtlMillis, name,
alternateNames, description, image, url, potentialActions);
mEnabled = enabled;
@@ -102,7 +110,7 @@
mShouldVibrate = shouldVibrate;
mPreviousInstance = previousInstance;
mNextInstance = nextInstance;
- mComputingDevice = computingDevice;
+ mOriginatingDevice = originatingDevice;
}
/** Returns whether or not the {@link Alarm} is active. */
@@ -217,10 +225,10 @@
return mNextInstance;
}
- /** Returns the {@link ComputingDevice} this alarm belongs to. */
- @ComputingDevice
- public int getComputingDevice() {
- return mComputingDevice;
+ /** Returns the {@link OriginatingDevice} this alarm belongs to. */
+ @OriginatingDevice
+ public int getOriginatingDevice() {
+ return mOriginatingDevice;
}
/** Builder for {@link Alarm}. */
@@ -257,7 +265,7 @@
protected boolean mShouldVibrate;
protected AlarmInstance mPreviousInstance;
protected AlarmInstance mNextInstance;
- protected int mComputingDevice;
+ protected int mOriginatingDevice;
BuilderImpl(@NonNull String namespace, @NonNull String id) {
super(namespace, id);
@@ -275,7 +283,7 @@
mShouldVibrate = alarm.shouldVibrate();
mPreviousInstance = alarm.getPreviousInstance();
mNextInstance = alarm.getNextInstance();
- mComputingDevice = alarm.getComputingDevice();
+ mOriginatingDevice = alarm.getOriginatingDevice();
}
/** Sets whether or not the {@link Alarm} is active. */
@@ -421,10 +429,10 @@
return (T) this;
}
- /** Sets the {@link ComputingDevice} this alarm belongs to. */
+ /** Sets the {@link OriginatingDevice} this alarm belongs to. */
@NonNull
- public T setComputingDevice(@ComputingDevice int computingDevice) {
- mComputingDevice = computingDevice;
+ public T setOriginatingDevice(@OriginatingDevice int originatingDevice) {
+ mOriginatingDevice = originatingDevice;
return (T) this;
}
@@ -437,7 +445,7 @@
mPotentialActions,
mEnabled, mDaysOfWeek, mHour, mMinute, mBlackoutPeriodStartDate,
mBlackoutPeriodEndDate, mRingtone, mShouldVibrate, mPreviousInstance,
- mNextInstance, mComputingDevice);
+ mNextInstance, mOriginatingDevice);
}
}
}
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
index 5cecaa7..35e4404 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
@@ -50,7 +50,7 @@
// fall through
case Features.LIST_FILTER_HAS_PROPERTY_FUNCTION:
// fall through
- case Features.LIST_FILTER_TOKENIZE_FUNCTION:
+ case Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS:
// fall through
case Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG:
// fall through
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java
index ad5be47..b7ee775 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java
@@ -289,9 +289,10 @@
.setQuery(mQueryExpression)
.addAllNamespaceFilters(mTargetPrefixedNamespaceFilters)
.addAllSchemaTypeFilters(mTargetPrefixedSchemaFilters)
- .setUseReadOnlySearch(mIcingOptionsConfig.getUseReadOnlySearch());
+ .setUseReadOnlySearch(mIcingOptionsConfig.getUseReadOnlySearch())
+ .addAllQueryParameterStrings(mSearchSpec.getSearchStringParameters());
- List<EmbeddingVector> searchEmbeddings = mSearchSpec.getSearchEmbeddings();
+ List<EmbeddingVector> searchEmbeddings = mSearchSpec.getEmbeddingParameters();
for (int i = 0; i < searchEmbeddings.size(); i++) {
protoBuilder.addEmbeddingQueryVectors(
GenericDocumentToProtoConverter.embeddingVectorToVectorProto(
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSuggestionSpecToProtoConverter.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSuggestionSpecToProtoConverter.java
index 7c99bb2..dfcea5f 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSuggestionSpecToProtoConverter.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSuggestionSpecToProtoConverter.java
@@ -107,7 +107,8 @@
.setPrefix(mSuggestionQueryExpression)
.addAllNamespaceFilters(mTargetPrefixedNamespaceFilters)
.addAllSchemaTypeFilters(mTargetPrefixedSchemaFilters)
- .setNumToReturn(mSearchSuggestionSpec.getMaximumResultCount());
+ .setNumToReturn(mSearchSuggestionSpec.getMaximumResultCount())
+ .addAllQueryParameterStrings(mSearchSuggestionSpec.getSearchStringParameters());
// Convert type property filter map into type property mask proto.
for (Map.Entry<String, List<String>> entry :
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
index aa0b554..cf8406b 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
@@ -97,7 +97,7 @@
case Features.SCHEMA_SET_DESCRIPTION:
// TODO(b/326987971) : Update when feature is ready in service-appsearch.
// fall through
- case Features.LIST_FILTER_TOKENIZE_FUNCTION:
+ case Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS:
// TODO(b/332620561) : Update when feature is ready in service-appsearch.
// fall through
case Features.SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS:
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSpecToPlatformConverter.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSpecToPlatformConverter.java
index 125da54..5239d56 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSpecToPlatformConverter.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSpecToPlatformConverter.java
@@ -125,18 +125,16 @@
}
ApiHelperForV.copyEnabledFeatures(platformBuilder, jetpackSearchSpec);
}
- // Copy beyond-V features
- if (jetpackSearchSpec.isEmbeddingSearchEnabled()
- || !jetpackSearchSpec.getSearchEmbeddings().isEmpty()) {
- // TODO(b/326656531): Remove this once embedding search APIs are available.
- throw new UnsupportedOperationException(Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG
- + " is not available on this AppSearch implementation.");
- }
- if (jetpackSearchSpec.isListFilterTokenizeFunctionEnabled()) {
- // TODO(b/332620561): Remove this once 'tokenize' is supported.
- throw new UnsupportedOperationException(Features.LIST_FILTER_TOKENIZE_FUNCTION
- + " is not available on this AppSearch implementation.");
- }
+ }
+ if (!jetpackSearchSpec.getEmbeddingParameters().isEmpty()) {
+ // TODO(b/326656531): Remove this once embedding search APIs are available.
+ throw new UnsupportedOperationException(Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG
+ + " is not available on this AppSearch implementation.");
+ }
+ if (!jetpackSearchSpec.getSearchStringParameters().isEmpty()) {
+ // TODO(b/332620561): Remove this once search parameter strings APIs is supported.
+ throw new UnsupportedOperationException(Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS
+ + " is not available on this AppSearch implementation.");
}
if (jetpackSearchSpec.getJoinSpec() != null) {
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSuggestionSpecToPlatformConverter.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSuggestionSpecToPlatformConverter.java
index d354fe5..e16ce71 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSuggestionSpecToPlatformConverter.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSuggestionSpecToPlatformConverter.java
@@ -78,6 +78,12 @@
platformBuilder, entry.getKey(), entry.getValue());
}
}
+ if (!jetpackSearchSuggestionSpec.getSearchStringParameters().isEmpty()) {
+ // TODO(b/332620561): Remove this once search parameter strings APIs is supported.
+ throw new UnsupportedOperationException(
+ Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS
+ + " is not available on this AppSearch implementation.");
+ }
return platformBuilder.build();
}
diff --git a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java
index 6393a15..3e93d3e 100644
--- a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java
+++ b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java
@@ -80,7 +80,7 @@
// fall through
case Features.SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS:
// fall through
- case Features.LIST_FILTER_TOKENIZE_FUNCTION:
+ case Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS:
// fall through
default:
return false; // AppSearch features absent in GMSCore AppSearch.
diff --git a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSpecToGmsConverter.java b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSpecToGmsConverter.java
index 0d687c0..4e92e8a 100644
--- a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSpecToGmsConverter.java
+++ b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSpecToGmsConverter.java
@@ -87,18 +87,16 @@
if (jetpackSearchSpec.isListFilterHasPropertyFunctionEnabled()) {
gmsBuilder.setListFilterHasPropertyFunctionEnabled(true);
}
- // Copy beyond-V features
- if (jetpackSearchSpec.isEmbeddingSearchEnabled()
- || !jetpackSearchSpec.getSearchEmbeddings().isEmpty()) {
- // TODO(b/326656531): Remove this once embedding search APIs are available.
- throw new UnsupportedOperationException(Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG
- + " is not available on this AppSearch implementation.");
- }
- if (jetpackSearchSpec.isListFilterTokenizeFunctionEnabled()) {
- // TODO(b/332620561): Remove this once 'tokenize' is supported.
- throw new UnsupportedOperationException(Features.LIST_FILTER_TOKENIZE_FUNCTION
- + " is not available on this AppSearch implementation.");
- }
+ }
+ if (!jetpackSearchSpec.getEmbeddingParameters().isEmpty()) {
+ // TODO(b/326656531): Remove this once embedding search APIs are available.
+ throw new UnsupportedOperationException(Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG
+ + " is not available on this AppSearch implementation.");
+ }
+ if (!jetpackSearchSpec.getSearchStringParameters().isEmpty()) {
+ // TODO(b/332620561): Remove this once search parameter strings are supported.
+ throw new UnsupportedOperationException(Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS
+ + " is not available on this AppSearch implementation.");
}
if (jetpackSearchSpec.getJoinSpec() != null) {
diff --git a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSuggestionSpecToGmsConverter.java b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSuggestionSpecToGmsConverter.java
index 18556a7..119758a 100644
--- a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSuggestionSpecToGmsConverter.java
+++ b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSuggestionSpecToGmsConverter.java
@@ -20,6 +20,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
+import androidx.appsearch.app.Features;
import androidx.appsearch.app.SearchSuggestionSpec;
import androidx.core.util.Preconditions;
@@ -65,6 +66,13 @@
gmsBuilder.addFilterProperties(entry.getKey(), entry.getValue());
}
}
+
+ if (!jetpackSearchSuggestionSpec.getSearchStringParameters().isEmpty()) {
+ // TODO(b/332620561): Remove this once search parameter strings are supported.
+ throw new UnsupportedOperationException(
+ Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS
+ + " is not available on this AppSearch implementation.");
+ }
return gmsBuilder.build();
}
}
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index 9ed73ae..efdb86e 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -302,7 +302,6 @@
field public static final String JOIN_SPEC_AND_QUALIFIED_ID = "JOIN_SPEC_AND_QUALIFIED_ID";
field public static final String LIST_FILTER_HAS_PROPERTY_FUNCTION = "LIST_FILTER_HAS_PROPERTY_FUNCTION";
field public static final String LIST_FILTER_QUERY_LANGUAGE = "LIST_FILTER_QUERY_LANGUAGE";
- field public static final String LIST_FILTER_TOKENIZE_FUNCTION = "LIST_FILTER_TOKENIZE_FUNCTION";
field public static final String NUMERIC_SEARCH = "NUMERIC_SEARCH";
field public static final String SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES = "SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES";
field public static final String SCHEMA_ADD_PARENT_TYPE = "SCHEMA_ADD_PARENT_TYPE";
@@ -314,6 +313,7 @@
field public static final String SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION = "SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION";
field public static final String SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA = "SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA";
field public static final String SEARCH_SPEC_PROPERTY_WEIGHTS = "SEARCH_SPEC_PROPERTY_WEIGHTS";
+ field public static final String SEARCH_SPEC_SEARCH_STRING_PARAMETERS = "SEARCH_SPEC_SEARCH_STRING_PARAMETERS";
field public static final String SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG = "SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG";
field public static final String SEARCH_SUGGESTION = "SEARCH_SUGGESTION";
field public static final String SET_SCHEMA_CIRCULAR_REFERENCES = "SET_SCHEMA_CIRCULAR_REFERENCES";
@@ -608,6 +608,7 @@
public final class SearchSpec {
method public String getAdvancedRankingExpression();
method public int getDefaultEmbeddingSearchMetricType();
+ method public java.util.List<androidx.appsearch.app.EmbeddingVector!> getEmbeddingParameters();
method public java.util.List<java.lang.String!> getFilterNamespaces();
method public java.util.List<java.lang.String!> getFilterPackageNames();
method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getFilterProperties();
@@ -624,15 +625,15 @@
method public int getResultCountPerPage();
method public int getResultGroupingLimit();
method public int getResultGroupingTypeFlags();
- method public java.util.List<androidx.appsearch.app.EmbeddingVector!> getSearchEmbeddings();
+ method @Deprecated public java.util.List<androidx.appsearch.app.EmbeddingVector!> getSearchEmbeddings();
method public String? getSearchSourceLogTag();
+ method public java.util.List<java.lang.String!> getSearchStringParameters();
method public int getSnippetCount();
method public int getSnippetCountPerProperty();
method public int getTermMatch();
- method public boolean isEmbeddingSearchEnabled();
+ method @Deprecated public boolean isEmbeddingSearchEnabled();
method public boolean isListFilterHasPropertyFunctionEnabled();
method public boolean isListFilterQueryLanguageEnabled();
- method public boolean isListFilterTokenizeFunctionEnabled();
method public boolean isNumericSearchEnabled();
method public boolean isVerbatimSearchEnabled();
field public static final int EMBEDDING_SEARCH_METRIC_TYPE_COSINE = 1; // 0x1
@@ -661,6 +662,8 @@
public static final class SearchSpec.Builder {
ctor public SearchSpec.Builder();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(androidx.appsearch.app.EmbeddingVector!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
method public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
method public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentClasses(java.util.Collection<? extends java.lang.Class<? extends java.lang.Object!>!>) throws androidx.appsearch.exceptions.AppSearchException;
method public androidx.appsearch.app.SearchSpec.Builder addFilterNamespaces(java.lang.String!...);
@@ -679,15 +682,16 @@
method public androidx.appsearch.app.SearchSpec.Builder addProjectionPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
method public androidx.appsearch.app.SearchSpec.Builder addProjectionPathsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Collection<androidx.appsearch.app.PropertyPath!>) throws androidx.appsearch.exceptions.AppSearchException;
method public androidx.appsearch.app.SearchSpec.Builder addProjectionsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Collection<java.lang.String!>) throws androidx.appsearch.exceptions.AppSearchException;
- method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(androidx.appsearch.app.EmbeddingVector!...);
- method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
+ method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(androidx.appsearch.app.EmbeddingVector!...);
+ method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.lang.String!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.util.List<java.lang.String!>);
method public androidx.appsearch.app.SearchSpec build();
method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setDefaultEmbeddingSearchMetricType(int);
- method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setEmbeddingSearchEnabled(boolean);
+ method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setEmbeddingSearchEnabled(boolean);
method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) public androidx.appsearch.app.SearchSpec.Builder setJoinSpec(androidx.appsearch.app.JoinSpec);
method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_HAS_PROPERTY_FUNCTION) public androidx.appsearch.app.SearchSpec.Builder setListFilterHasPropertyFunctionEnabled(boolean);
method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_QUERY_LANGUAGE) public androidx.appsearch.app.SearchSpec.Builder setListFilterQueryLanguageEnabled(boolean);
- method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_TOKENIZE_FUNCTION) public androidx.appsearch.app.SearchSpec.Builder setListFilterTokenizeFunctionEnabled(boolean);
method public androidx.appsearch.app.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=0x2710) int);
method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.NUMERIC_SEARCH) public androidx.appsearch.app.SearchSpec.Builder setNumericSearchEnabled(boolean);
method public androidx.appsearch.app.SearchSpec.Builder setOrder(int);
@@ -723,6 +727,7 @@
method public java.util.List<java.lang.String!> getFilterSchemas();
method public int getMaximumResultCount();
method public int getRankingStrategy();
+ method public java.util.List<java.lang.String!> getSearchStringParameters();
field public static final int SUGGESTION_RANKING_STRATEGY_DOCUMENT_COUNT = 0; // 0x0
field public static final int SUGGESTION_RANKING_STRATEGY_NONE = 2; // 0x2
field public static final int SUGGESTION_RANKING_STRATEGY_TERM_FREQUENCY = 1; // 0x1
@@ -742,6 +747,8 @@
method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterPropertyPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterSchemas(java.lang.String!...);
method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterSchemas(java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSuggestionSpec.Builder addSearchStringParameters(java.lang.String!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSuggestionSpec.Builder addSearchStringParameters(java.util.List<java.lang.String!>);
method public androidx.appsearch.app.SearchSuggestionSpec build();
method public androidx.appsearch.app.SearchSuggestionSpec.Builder setRankingStrategy(int);
}
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index 9ed73ae..efdb86e 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -302,7 +302,6 @@
field public static final String JOIN_SPEC_AND_QUALIFIED_ID = "JOIN_SPEC_AND_QUALIFIED_ID";
field public static final String LIST_FILTER_HAS_PROPERTY_FUNCTION = "LIST_FILTER_HAS_PROPERTY_FUNCTION";
field public static final String LIST_FILTER_QUERY_LANGUAGE = "LIST_FILTER_QUERY_LANGUAGE";
- field public static final String LIST_FILTER_TOKENIZE_FUNCTION = "LIST_FILTER_TOKENIZE_FUNCTION";
field public static final String NUMERIC_SEARCH = "NUMERIC_SEARCH";
field public static final String SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES = "SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES";
field public static final String SCHEMA_ADD_PARENT_TYPE = "SCHEMA_ADD_PARENT_TYPE";
@@ -314,6 +313,7 @@
field public static final String SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION = "SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION";
field public static final String SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA = "SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA";
field public static final String SEARCH_SPEC_PROPERTY_WEIGHTS = "SEARCH_SPEC_PROPERTY_WEIGHTS";
+ field public static final String SEARCH_SPEC_SEARCH_STRING_PARAMETERS = "SEARCH_SPEC_SEARCH_STRING_PARAMETERS";
field public static final String SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG = "SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG";
field public static final String SEARCH_SUGGESTION = "SEARCH_SUGGESTION";
field public static final String SET_SCHEMA_CIRCULAR_REFERENCES = "SET_SCHEMA_CIRCULAR_REFERENCES";
@@ -608,6 +608,7 @@
public final class SearchSpec {
method public String getAdvancedRankingExpression();
method public int getDefaultEmbeddingSearchMetricType();
+ method public java.util.List<androidx.appsearch.app.EmbeddingVector!> getEmbeddingParameters();
method public java.util.List<java.lang.String!> getFilterNamespaces();
method public java.util.List<java.lang.String!> getFilterPackageNames();
method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getFilterProperties();
@@ -624,15 +625,15 @@
method public int getResultCountPerPage();
method public int getResultGroupingLimit();
method public int getResultGroupingTypeFlags();
- method public java.util.List<androidx.appsearch.app.EmbeddingVector!> getSearchEmbeddings();
+ method @Deprecated public java.util.List<androidx.appsearch.app.EmbeddingVector!> getSearchEmbeddings();
method public String? getSearchSourceLogTag();
+ method public java.util.List<java.lang.String!> getSearchStringParameters();
method public int getSnippetCount();
method public int getSnippetCountPerProperty();
method public int getTermMatch();
- method public boolean isEmbeddingSearchEnabled();
+ method @Deprecated public boolean isEmbeddingSearchEnabled();
method public boolean isListFilterHasPropertyFunctionEnabled();
method public boolean isListFilterQueryLanguageEnabled();
- method public boolean isListFilterTokenizeFunctionEnabled();
method public boolean isNumericSearchEnabled();
method public boolean isVerbatimSearchEnabled();
field public static final int EMBEDDING_SEARCH_METRIC_TYPE_COSINE = 1; // 0x1
@@ -661,6 +662,8 @@
public static final class SearchSpec.Builder {
ctor public SearchSpec.Builder();
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(androidx.appsearch.app.EmbeddingVector!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
method public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
method public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentClasses(java.util.Collection<? extends java.lang.Class<? extends java.lang.Object!>!>) throws androidx.appsearch.exceptions.AppSearchException;
method public androidx.appsearch.app.SearchSpec.Builder addFilterNamespaces(java.lang.String!...);
@@ -679,15 +682,16 @@
method public androidx.appsearch.app.SearchSpec.Builder addProjectionPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
method public androidx.appsearch.app.SearchSpec.Builder addProjectionPathsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Collection<androidx.appsearch.app.PropertyPath!>) throws androidx.appsearch.exceptions.AppSearchException;
method public androidx.appsearch.app.SearchSpec.Builder addProjectionsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Collection<java.lang.String!>) throws androidx.appsearch.exceptions.AppSearchException;
- method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(androidx.appsearch.app.EmbeddingVector!...);
- method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
+ method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(androidx.appsearch.app.EmbeddingVector!...);
+ method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.lang.String!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.util.List<java.lang.String!>);
method public androidx.appsearch.app.SearchSpec build();
method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setDefaultEmbeddingSearchMetricType(int);
- method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setEmbeddingSearchEnabled(boolean);
+ method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setEmbeddingSearchEnabled(boolean);
method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) public androidx.appsearch.app.SearchSpec.Builder setJoinSpec(androidx.appsearch.app.JoinSpec);
method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_HAS_PROPERTY_FUNCTION) public androidx.appsearch.app.SearchSpec.Builder setListFilterHasPropertyFunctionEnabled(boolean);
method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_QUERY_LANGUAGE) public androidx.appsearch.app.SearchSpec.Builder setListFilterQueryLanguageEnabled(boolean);
- method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_TOKENIZE_FUNCTION) public androidx.appsearch.app.SearchSpec.Builder setListFilterTokenizeFunctionEnabled(boolean);
method public androidx.appsearch.app.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=0x2710) int);
method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.NUMERIC_SEARCH) public androidx.appsearch.app.SearchSpec.Builder setNumericSearchEnabled(boolean);
method public androidx.appsearch.app.SearchSpec.Builder setOrder(int);
@@ -723,6 +727,7 @@
method public java.util.List<java.lang.String!> getFilterSchemas();
method public int getMaximumResultCount();
method public int getRankingStrategy();
+ method public java.util.List<java.lang.String!> getSearchStringParameters();
field public static final int SUGGESTION_RANKING_STRATEGY_DOCUMENT_COUNT = 0; // 0x0
field public static final int SUGGESTION_RANKING_STRATEGY_NONE = 2; // 0x2
field public static final int SUGGESTION_RANKING_STRATEGY_TERM_FREQUENCY = 1; // 0x1
@@ -742,6 +747,8 @@
method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterPropertyPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterSchemas(java.lang.String!...);
method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterSchemas(java.util.Collection<java.lang.String!>);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSuggestionSpec.Builder addSearchStringParameters(java.lang.String!...);
+ method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSuggestionSpec.Builder addSearchStringParameters(java.util.List<java.lang.String!>);
method public androidx.appsearch.app.SearchSuggestionSpec build();
method public androidx.appsearch.app.SearchSuggestionSpec.Builder setRankingStrategy(int);
}
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
index 7d18f74..23cdf04 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
@@ -2867,30 +2867,28 @@
assertThat(outputDocument).isEqualTo(email);
// senderEmbedding is non-indexable, so querying for it will return nothing.
- searchResults = mSession.search("semanticSearch(getSearchSpecEmbedding(0), 0.9, 1)",
+ searchResults = mSession.search("semanticSearch(getEmbeddingParameter(0), 0.9, 1)",
new SearchSpec.Builder()
.setDefaultEmbeddingSearchMetricType(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE)
- .addSearchEmbeddings(email.mSenderEmbedding)
+ .addEmbeddingParameters(email.mSenderEmbedding)
.setRankingStrategy(
- "sum(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+ "sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
.setListFilterQueryLanguageEnabled(true)
- .setEmbeddingSearchEnabled(true)
.build());
results = retrieveAllSearchResults(searchResults);
assertThat(results).isEmpty();
// titleEmbedding is indexable, and querying for it using itself will return a cosine
// similarity score of 1.
- searchResults = mSession.search("semanticSearch(getSearchSpecEmbedding(0), 0.9, 1)",
+ searchResults = mSession.search("semanticSearch(getEmbeddingParameter(0), 0.9, 1)",
new SearchSpec.Builder()
.setDefaultEmbeddingSearchMetricType(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE)
- .addSearchEmbeddings(email.mTitleEmbedding)
+ .addEmbeddingParameters(email.mTitleEmbedding)
.setRankingStrategy(
- "sum(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+ "sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
.setListFilterQueryLanguageEnabled(true)
- .setEmbeddingSearchEnabled(true)
.build());
results = retrieveAllSearchResults(searchResults);
assertThat(results).hasSize(1);
@@ -2901,18 +2899,17 @@
// Both receiverEmbeddings and bodyEmbeddings are indexable, and in this specific
// document, they together hold three embedding vectors with the same signature.
- searchResults = mSession.search("semanticSearch(getSearchSpecEmbedding(0), -1, 1)",
+ searchResults = mSession.search("semanticSearch(getEmbeddingParameter(0), -1, 1)",
new SearchSpec.Builder()
.setDefaultEmbeddingSearchMetricType(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE)
// Using one of the three vectors to query
- .addSearchEmbeddings(email.mBodyEmbeddings[0])
+ .addEmbeddingParameters(email.mBodyEmbeddings[0])
.setRankingStrategy(
// We should get a score of 3 for "len", since there are three
// embedding vectors matched.
- "len(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+ "len(this.matchedSemanticScores(getEmbeddingParameter(0)))")
.setListFilterQueryLanguageEnabled(true)
- .setEmbeddingSearchEnabled(true)
.build());
results = retrieveAllSearchResults(searchResults);
assertThat(results).hasSize(1);
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecInternalTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecInternalTest.java
index e733fc3..5f0fbb6 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecInternalTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecInternalTest.java
@@ -127,10 +127,9 @@
new float[]{4.4f, 5.5f, 6.6f, 7.7f}, "my_model_v2");
SearchSpec searchSpec = new SearchSpec.Builder()
.setListFilterQueryLanguageEnabled(true)
- .setEmbeddingSearchEnabled(true)
.setDefaultEmbeddingSearchMetricType(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
- .addSearchEmbeddings(embedding1, embedding2)
+ .addEmbeddingParameters(embedding1, embedding2)
.build();
// Check that copy constructor works.
@@ -139,8 +138,8 @@
searchSpec.getEnabledFeatures());
assertThat(searchSpecCopy.getDefaultEmbeddingSearchMetricType()).isEqualTo(
searchSpec.getDefaultEmbeddingSearchMetricType());
- assertThat(searchSpecCopy.getSearchEmbeddings()).containsExactlyElementsIn(
- searchSpec.getSearchEmbeddings());
+ assertThat(searchSpecCopy.getEmbeddingParameters()).containsExactlyElementsIn(
+ searchSpec.getEmbeddingParameters());
}
@Test
@@ -191,47 +190,4 @@
assertThat(searchSpec3.getEnabledFeatures()).containsExactly(
Features.VERBATIM_SEARCH, Features.LIST_FILTER_QUERY_LANGUAGE);
}
-
- @Test
- public void testGetEnabledFeatures_embeddingSearch() {
- SearchSpec searchSpec = new SearchSpec.Builder()
- .setNumericSearchEnabled(true)
- .setVerbatimSearchEnabled(true)
- .setListFilterQueryLanguageEnabled(true)
- .setListFilterHasPropertyFunctionEnabled(true)
- .setEmbeddingSearchEnabled(true)
- .build();
- assertThat(searchSpec.getEnabledFeatures()).containsExactly(
- Features.NUMERIC_SEARCH, Features.VERBATIM_SEARCH,
- Features.LIST_FILTER_QUERY_LANGUAGE, Features.LIST_FILTER_HAS_PROPERTY_FUNCTION,
- FeatureConstants.EMBEDDING_SEARCH);
-
- // Check that copy constructor works.
- SearchSpec searchSpecCopy = new SearchSpec.Builder(searchSpec).build();
- assertThat(searchSpecCopy.getEnabledFeatures()).containsExactly(
- Features.NUMERIC_SEARCH, Features.VERBATIM_SEARCH,
- Features.LIST_FILTER_QUERY_LANGUAGE, Features.LIST_FILTER_HAS_PROPERTY_FUNCTION,
- FeatureConstants.EMBEDDING_SEARCH);
- }
-
- @Test
- public void testGetEnabledFeatures_tokenize() {
- SearchSpec searchSpec = new SearchSpec.Builder()
- .setNumericSearchEnabled(true)
- .setVerbatimSearchEnabled(true)
- .setListFilterQueryLanguageEnabled(true)
- .setListFilterTokenizeFunctionEnabled(true)
- .build();
- assertThat(searchSpec.getEnabledFeatures()).containsExactly(
- Features.NUMERIC_SEARCH, Features.VERBATIM_SEARCH,
- Features.LIST_FILTER_QUERY_LANGUAGE,
- FeatureConstants.LIST_FILTER_TOKENIZE_FUNCTION);
-
- // Check that copy constructor works.
- SearchSpec searchSpecCopy = new SearchSpec.Builder(searchSpec).build();
- assertThat(searchSpecCopy.getEnabledFeatures()).containsExactly(
- Features.NUMERIC_SEARCH, Features.VERBATIM_SEARCH,
- Features.LIST_FILTER_QUERY_LANGUAGE,
- FeatureConstants.LIST_FILTER_TOKENIZE_FUNCTION);
- }
}
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
index 97698a7..6260cef 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
@@ -8713,14 +8713,13 @@
SearchSpec searchSpec = new SearchSpec.Builder()
.setDefaultEmbeddingSearchMetricType(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
- .addSearchEmbeddings(searchEmbedding)
+ .addEmbeddingParameters(searchEmbedding)
.setRankingStrategy(
- "sum(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+ "sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
.setListFilterQueryLanguageEnabled(true)
- .setEmbeddingSearchEnabled(true)
.build();
SearchResults searchResults = mDb1.search(
- "semanticSearch(getSearchSpecEmbedding(0), -1, 1)", searchSpec);
+ "semanticSearch(getEmbeddingParameter(0), -1, 1)", searchSpec);
List<SearchResult> results = retrieveAllSearchResults(searchResults);
assertThat(results).hasSize(2);
assertThat(results.get(0).getGenericDocument()).isEqualTo(doc0);
@@ -8798,13 +8797,12 @@
SearchSpec searchSpec = new SearchSpec.Builder()
.setDefaultEmbeddingSearchMetricType(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
- .addSearchEmbeddings(searchEmbedding)
- .setRankingStrategy("sum(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+ .addEmbeddingParameters(searchEmbedding)
+ .setRankingStrategy("sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
.setListFilterQueryLanguageEnabled(true)
- .setEmbeddingSearchEnabled(true)
.build();
SearchResults searchResults = mDb1.search(
- "embedding1:semanticSearch(getSearchSpecEmbedding(0), -1, 1)", searchSpec);
+ "embedding1:semanticSearch(getEmbeddingParameter(0), -1, 1)", searchSpec);
List<SearchResult> results = retrieveAllSearchResults(searchResults);
assertThat(results).hasSize(2);
assertThat(results.get(0).getGenericDocument()).isEqualTo(doc0);
@@ -8886,15 +8884,14 @@
SearchSpec searchSpec = new SearchSpec.Builder()
.setDefaultEmbeddingSearchMetricType(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
- .addSearchEmbeddings(searchEmbedding1, searchEmbedding2)
- .setRankingStrategy("sum(this.matchedSemanticScores(getSearchSpecEmbedding(0))) + "
- + "sum(this.matchedSemanticScores(getSearchSpecEmbedding(1)))")
+ .addEmbeddingParameters(searchEmbedding1, searchEmbedding2)
+ .setRankingStrategy("sum(this.matchedSemanticScores(getEmbeddingParameter(0))) + "
+ + "sum(this.matchedSemanticScores(getEmbeddingParameter(1)))")
.setListFilterQueryLanguageEnabled(true)
- .setEmbeddingSearchEnabled(true)
.build();
SearchResults searchResults = mDb1.search(
- "semanticSearch(getSearchSpecEmbedding(0)) OR "
- + "semanticSearch(getSearchSpecEmbedding(1))", searchSpec);
+ "semanticSearch(getEmbeddingParameter(0)) OR "
+ + "semanticSearch(getEmbeddingParameter(1))", searchSpec);
List<SearchResult> results = retrieveAllSearchResults(searchResults);
assertThat(results).hasSize(2);
assertThat(results.get(0).getGenericDocument()).isEqualTo(doc0);
@@ -8971,13 +8968,12 @@
SearchSpec searchSpec = new SearchSpec.Builder()
.setDefaultEmbeddingSearchMetricType(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
- .addSearchEmbeddings(searchEmbedding)
- .setRankingStrategy("sum(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+ .addEmbeddingParameters(searchEmbedding)
+ .setRankingStrategy("sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
.setListFilterQueryLanguageEnabled(true)
- .setEmbeddingSearchEnabled(true)
.build();
SearchResults searchResults = mDb1.search(
- "foo OR semanticSearch(getSearchSpecEmbedding(0), -10, -1)", searchSpec);
+ "foo OR semanticSearch(getEmbeddingParameter(0), -10, -1)", searchSpec);
List<SearchResult> results = retrieveAllSearchResults(searchResults);
assertThat(results).hasSize(2);
assertThat(results.get(0).getGenericDocument()).isEqualTo(doc0);
@@ -8988,89 +8984,33 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
- public void testEmbeddingSearchWithoutEnablingFeatureFails() throws Exception {
- assumeTrue(
- mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG));
-
- // Schema registration
- AppSearchSchema schema = new AppSearchSchema.Builder("Email")
- .addProperty(new StringPropertyConfig.Builder("body")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .build())
- .addProperty(new AppSearchSchema.EmbeddingPropertyConfig.Builder("embedding1")
- .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
- .build())
- .addProperty(new AppSearchSchema.EmbeddingPropertyConfig.Builder("embedding2")
- .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
- .build())
- .build();
- mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(schema).build()).get();
-
- // Index documents
- GenericDocument doc =
- new GenericDocument.Builder<>("namespace", "id0", "Email")
- .setPropertyString("body", "foo")
- .setCreationTimestampMillis(1000)
- .setPropertyEmbedding("embedding1", new EmbeddingVector(
- new float[]{0.1f, 0.2f, 0.3f, 0.4f, 0.5f}, "my_model_v1"))
- .setPropertyEmbedding("embedding2", new EmbeddingVector(
- new float[]{-0.1f, -0.2f, -0.3f, 0.4f, 0.5f},
- "my_model_v1"),
- new EmbeddingVector(
- new float[]{0.6f, 0.7f, 0.8f}, "my_model_v2"))
- .build();
- checkIsBatchResultSuccess(mDb1.putAsync(
- new PutDocumentsRequest.Builder().addGenericDocuments(doc).build()));
-
- EmbeddingVector searchEmbedding = new EmbeddingVector(
- new float[]{1, -1, -1, 1, -1}, "my_model_v1");
- SearchSpec searchSpec = new SearchSpec.Builder()
- .setDefaultEmbeddingSearchMetricType(
- SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
- .addSearchEmbeddings(searchEmbedding)
- .setRankingStrategy(
- "sum(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
- .setListFilterQueryLanguageEnabled(true)
- .build();
- SearchResults searchResults = mDb1.search(
- "semanticSearch(getSearchSpecEmbedding(0), -1, 1)", searchSpec);
- ExecutionException executionException = assertThrows(ExecutionException.class,
- () -> searchResults.getNextPageAsync().get());
- assertThat(executionException).hasCauseThat().isInstanceOf(AppSearchException.class);
- AppSearchException exception = (AppSearchException) executionException.getCause();
- assertThat(exception.getResultCode()).isEqualTo(RESULT_INVALID_ARGUMENT);
- assertThat(exception).hasMessageThat().contains("Attempted use of unenabled feature");
- assertThat(exception).hasMessageThat().contains("EMBEDDING_SEARCH");
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
public void testEmbeddingSearch_notSupported() throws Exception {
assumeTrue(
mDb1.getFeatures().isFeatureSupported(Features.LIST_FILTER_QUERY_LANGUAGE));
assumeFalse(
mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG));
+ EmbeddingVector searchEmbedding = new EmbeddingVector(
+ new float[]{-1, -1, 1}, "my_model_v2");
SearchSpec searchSpec = new SearchSpec.Builder()
.setListFilterQueryLanguageEnabled(true)
- .setEmbeddingSearchEnabled(true)
+ .addEmbeddingParameters(searchEmbedding)
.build();
UnsupportedOperationException exception = assertThrows(
UnsupportedOperationException.class,
- () -> mDb1.search("semanticSearch(getSearchSpecEmbedding(0), -1, 1)", searchSpec));
+ () -> mDb1.search("semanticSearch(getEmbeddingParameter(0), -1, 1)", searchSpec));
assertThat(exception).hasMessageThat().contains(Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG
+ " is not available on this AppSearch implementation.");
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION)
- public void testTokenizeSearch_simple() throws Exception {
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ public void testSearchSpecStrings_simple() throws Exception {
assumeTrue(
mDb1.getFeatures().isFeatureSupported(Features.LIST_FILTER_QUERY_LANGUAGE));
assumeTrue(
- mDb1.getFeatures().isFeatureSupported(Features.LIST_FILTER_TOKENIZE_FUNCTION));
+ mDb1.getFeatures().isFeatureSupported(
+ Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS));
// Schema registration
AppSearchSchema schema = new AppSearchSchema.Builder("Email")
@@ -9104,45 +9044,67 @@
SearchSpec searchSpec = new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setListFilterQueryLanguageEnabled(true)
- .setListFilterTokenizeFunctionEnabled(true)
+ .addSearchStringParameters("foo.")
.build();
- SearchResults searchResults = mDb1.search("tokenize(\"foo.\")", searchSpec);
+ SearchResults searchResults = mDb1.search("getSearchStringParameter(0)", searchSpec);
List<GenericDocument> results = convertSearchResultsToDocuments(searchResults);
assertThat(results).containsExactly(doc2, doc0);
- searchResults = mDb1.search("tokenize(\"bar, foo\")", searchSpec);
+ searchSpec = new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setListFilterQueryLanguageEnabled(true)
+ .addSearchStringParameters("bar, foo")
+ .build();
+ searchResults = mDb1.search("getSearchStringParameter(0)", searchSpec);
results = convertSearchResultsToDocuments(searchResults);
assertThat(results).containsExactly(doc0);
- searchResults = mDb1.search("tokenize(\"\\\"bar, \\\"foo\\\"\")", searchSpec);
+ searchSpec = new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setListFilterQueryLanguageEnabled(true)
+ .addSearchStringParameters("\\\"bar, \\\"foo\\\"")
+ .build();
+ searchResults = mDb1.search("getSearchStringParameter(0)", searchSpec);
results = convertSearchResultsToDocuments(searchResults);
assertThat(results).containsExactly(doc0);
- searchResults = mDb1.search("tokenize(\"bar ) foo\")", searchSpec);
+ searchSpec = new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setListFilterQueryLanguageEnabled(true)
+ .addSearchStringParameters("bar ) foo")
+ .build();
+ searchResults = mDb1.search("getSearchStringParameter(0)", searchSpec);
results = convertSearchResultsToDocuments(searchResults);
assertThat(results).containsExactly(doc0);
- searchResults = mDb1.search("tokenize(\"bar foo(\")", searchSpec);
+ searchSpec = new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setListFilterQueryLanguageEnabled(true)
+ .addSearchStringParameters("bar foo(")
+ .build();
+ searchResults = mDb1.search("getSearchStringParameter(0)", searchSpec);
results = convertSearchResultsToDocuments(searchResults);
assertThat(results).containsExactly(doc0);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION)
- public void testTokenizeSearch_notSupported() throws Exception {
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ public void testSearchSpecString_notSupported() throws Exception {
assumeTrue(
mDb1.getFeatures().isFeatureSupported(Features.LIST_FILTER_QUERY_LANGUAGE));
assumeFalse(
- mDb1.getFeatures().isFeatureSupported(Features.LIST_FILTER_TOKENIZE_FUNCTION));
+ mDb1.getFeatures().isFeatureSupported(
+ Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS));
SearchSpec searchSpec = new SearchSpec.Builder()
.setListFilterQueryLanguageEnabled(true)
- .setListFilterTokenizeFunctionEnabled(true)
+ .addSearchStringParameters("bar foo(")
.build();
UnsupportedOperationException exception = assertThrows(
UnsupportedOperationException.class,
- () -> mDb1.search("tokenize(\"foo.\")", searchSpec));
- assertThat(exception).hasMessageThat().contains(Features.LIST_FILTER_TOKENIZE_FUNCTION
+ () -> mDb1.search("getSearchStringParameter(0)", searchSpec));
+ assertThat(exception).hasMessageThat().contains(
+ Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS
+ " is not available on this AppSearch implementation.");
}
@@ -9198,17 +9160,16 @@
SearchSpec searchSpec = new SearchSpec.Builder()
.setDefaultEmbeddingSearchMetricType(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
- .addSearchEmbeddings(searchEmbedding)
+ .addEmbeddingParameters(searchEmbedding)
.setRankingStrategy(
- "sum(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+ "sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
.addInformationalRankingExpressions(
- "len(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+ "len(this.matchedSemanticScores(getEmbeddingParameter(0)))")
.addInformationalRankingExpressions("this.documentScore()")
.setListFilterQueryLanguageEnabled(true)
- .setEmbeddingSearchEnabled(true)
.build();
SearchResults searchResults = mDb1.search(
- "semanticSearch(getSearchSpecEmbedding(0))", searchSpec);
+ "semanticSearch(getEmbeddingParameter(0))", searchSpec);
List<SearchResult> results = retrieveAllSearchResults(searchResults);
assertThat(results).hasSize(2);
// doc0:
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSpecCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSpecCtsTest.java
index a6820fb..5b5ac39 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSpecCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSpecCtsTest.java
@@ -40,6 +40,7 @@
import org.junit.Rule;
import org.junit.Test;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -686,16 +687,14 @@
new float[]{4.4f, 5.5f, 6.6f, 7.7f}, "my_model_v2");
SearchSpec searchSpec = new SearchSpec.Builder()
.setListFilterQueryLanguageEnabled(true)
- .setEmbeddingSearchEnabled(true)
.setDefaultEmbeddingSearchMetricType(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
- .addSearchEmbeddings(embedding1, embedding2)
+ .addEmbeddingParameters(embedding1, embedding2)
.build();
assertThat(searchSpec.isListFilterQueryLanguageEnabled()).isTrue();
- assertThat(searchSpec.isEmbeddingSearchEnabled()).isTrue();
assertThat(searchSpec.getDefaultEmbeddingSearchMetricType()).isEqualTo(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT);
- assertThat(searchSpec.getSearchEmbeddings()).containsExactly(embedding1, embedding2);
+ assertThat(searchSpec.getEmbeddingParameters()).containsExactly(embedding1, embedding2);
}
@Test
@@ -709,28 +708,25 @@
// Create a builder
SearchSpec.Builder searchSpecBuilder = new SearchSpec.Builder()
.setListFilterQueryLanguageEnabled(true)
- .setEmbeddingSearchEnabled(true)
.setDefaultEmbeddingSearchMetricType(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
- .addSearchEmbeddings(embedding1);
+ .addEmbeddingParameters(embedding1);
SearchSpec searchSpec1 = searchSpecBuilder.build();
// Add a new embedding to the builder and rebuild. We should see that the new embedding
// is only added to searchSpec2.
- searchSpecBuilder.addSearchEmbeddings(embedding2);
+ searchSpecBuilder.addEmbeddingParameters(embedding2);
SearchSpec searchSpec2 = searchSpecBuilder.build();
assertThat(searchSpec1.isListFilterQueryLanguageEnabled()).isTrue();
- assertThat(searchSpec1.isEmbeddingSearchEnabled()).isTrue();
assertThat(searchSpec1.getDefaultEmbeddingSearchMetricType()).isEqualTo(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT);
- assertThat(searchSpec1.getSearchEmbeddings()).containsExactly(embedding1);
+ assertThat(searchSpec1.getEmbeddingParameters()).containsExactly(embedding1);
assertThat(searchSpec2.isListFilterQueryLanguageEnabled()).isTrue();
- assertThat(searchSpec2.isEmbeddingSearchEnabled()).isTrue();
assertThat(searchSpec2.getDefaultEmbeddingSearchMetricType()).isEqualTo(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT);
- assertThat(searchSpec2.getSearchEmbeddings()).containsExactly(embedding1, embedding2);
+ assertThat(searchSpec2.getEmbeddingParameters()).containsExactly(embedding1, embedding2);
}
@Test
@@ -741,62 +737,12 @@
.setVerbatimSearchEnabled(true)
.setListFilterQueryLanguageEnabled(true)
.setListFilterHasPropertyFunctionEnabled(true)
- .setEmbeddingSearchEnabled(true)
.build();
assertThat(searchSpec.isNumericSearchEnabled()).isTrue();
assertThat(searchSpec.isVerbatimSearchEnabled()).isTrue();
assertThat(searchSpec.isListFilterQueryLanguageEnabled()).isTrue();
assertThat(searchSpec.isListFilterHasPropertyFunctionEnabled()).isTrue();
- assertThat(searchSpec.isEmbeddingSearchEnabled()).isTrue();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
- public void testSetFeatureEnabledToFalse_embeddingSearch() {
- SearchSpec.Builder builder = new SearchSpec.Builder();
- SearchSpec searchSpec = builder
- .setListFilterQueryLanguageEnabled(true)
- .setEmbeddingSearchEnabled(true)
- .build();
- assertThat(searchSpec.isListFilterQueryLanguageEnabled()).isTrue();
- assertThat(searchSpec.isEmbeddingSearchEnabled()).isTrue();
-
- searchSpec = builder
- .setListFilterQueryLanguageEnabled(false)
- .setEmbeddingSearchEnabled(false)
- .build();
- assertThat(searchSpec.isListFilterQueryLanguageEnabled()).isFalse();
- assertThat(searchSpec.isEmbeddingSearchEnabled()).isFalse();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION)
- public void testListFilterTokenizeFunction() {
- SearchSpec searchSpec = new SearchSpec.Builder()
- .setListFilterQueryLanguageEnabled(true)
- .setListFilterTokenizeFunctionEnabled(true)
- .build();
- assertThat(searchSpec.isListFilterQueryLanguageEnabled()).isTrue();
- assertThat(searchSpec.isListFilterTokenizeFunctionEnabled()).isTrue();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION)
- public void testSetFeatureEnabledToFalse_tokenizeFunction() {
- SearchSpec.Builder builder = new SearchSpec.Builder();
- SearchSpec searchSpec = builder
- .setListFilterQueryLanguageEnabled(true)
- .setListFilterTokenizeFunctionEnabled(true)
- .build();
- assertThat(searchSpec.isListFilterQueryLanguageEnabled()).isTrue();
- assertThat(searchSpec.isListFilterTokenizeFunctionEnabled()).isTrue();
-
- searchSpec = builder
- .setListFilterTokenizeFunctionEnabled(false)
- .build();
- assertThat(searchSpec.isListFilterQueryLanguageEnabled()).isTrue();
- assertThat(searchSpec.isListFilterTokenizeFunctionEnabled()).isFalse();
}
@Test
@@ -835,4 +781,45 @@
assertThat(rebuild.getInformationalRankingExpressions())
.containsExactly("this.relevanceScore()", "this.documentScore()").inOrder();
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ public void testSearchSpecStrings_default_isEmpty() {
+ SearchSpec searchSpec = new SearchSpec.Builder().build();
+ assertThat(searchSpec.getSearchStringParameters()).isEmpty();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ public void testSearchSpecStrings_addValues_areCumulative() {
+ SearchSpec.Builder searchSpecBuilder =
+ new SearchSpec.Builder().addSearchStringParameters("A", "b");
+ SearchSpec spec = searchSpecBuilder.build();
+ assertThat(spec.getSearchStringParameters()).containsExactly("A", "b").inOrder();
+
+ searchSpecBuilder.addSearchStringParameters(Arrays.asList("C", "d"));
+ spec = searchSpecBuilder.build();
+ assertThat(spec.getSearchStringParameters()).containsExactly("A", "b", "C", "d").inOrder();
+
+ searchSpecBuilder.addSearchStringParameters("e");
+ spec = searchSpecBuilder.build();
+ assertThat(spec.getSearchStringParameters())
+ .containsExactly("A", "b", "C", "d", "e").inOrder();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ public void testSearchSpecStrings_rebuild_doesntAffectOriginal() {
+ SearchSpec.Builder searchSpecBuilder =
+ new SearchSpec.Builder().addSearchStringParameters("A", "b");
+
+ SearchSpec original = searchSpecBuilder.build();
+ SearchSpec rebuild =
+ searchSpecBuilder.addSearchStringParameters(Arrays.asList("C", "d")).build();
+
+ // Rebuild won't effect the original object
+ assertThat(original.getSearchStringParameters()).containsExactly("A", "b").inOrder();
+ assertThat(rebuild.getSearchStringParameters())
+ .containsExactly("A", "b", "C", "d").inOrder();
+ }
}
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
index ee67901..06caa38 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
@@ -110,9 +110,10 @@
}
@Test
- public void testFlagValue_enableListFilterTokenizeFunction() {
- assertThat(Flags.FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION)
- .isEqualTo("com.android.appsearch.flags.enable_list_filter_tokenize_function");
+ public void testFlagValue_enableSearchSpecSearchStringParameters() {
+ assertThat(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ .isEqualTo(
+ "com.android.appsearch.flags.enable_search_spec_search_spec_strings");
}
@Test
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
index 58821b7..936a417 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
@@ -224,16 +224,16 @@
* documentB, regardless of whether a value is actually set.
*
* <p>{@link Features#SCHEMA_EMBEDDING_PROPERTY_CONFIG}: This feature covers the
- * "semanticSearch" and "getSearchSpecEmbedding" functions in query expressions, which are
+ * "semanticSearch" and "getEmbeddingParameter" functions in query expressions, which are
* used for semantic search.
*
- * <p>Usage: semanticSearch(getSearchSpecEmbedding({embedding_index}), {low}, {high}, {metric})
+ * <p>Usage: semanticSearch(getEmbeddingParameter({embedding_index}), {low}, {high}, {metric})
* <ul>
* <li>semanticSearch matches all documents that have at least one embedding vector with
* a matching model signature (see {@link EmbeddingVector#getModelSignature()}) and a
* similarity score within the range specified based on the provided metric.</li>
- * <li>getSearchSpecEmbedding({embedding_index}) retrieves the embedding search passed in
- * {@link SearchSpec.Builder#addSearchEmbeddings} based on the index specified, which
+ * <li>getEmbeddingParameter({embedding_index}) retrieves the embedding search passed in
+ * {@link SearchSpec.Builder#addEmbeddingParameters} based on the index specified, which
* starts from 0.</li>
* <li>"low" and "high" are floating point numbers that specify the similarity score
* range. If omitted, they default to negative and positive infinity, respectively.</li>
@@ -250,27 +250,27 @@
*
* <p>Examples:
* <ul>
- * <li>Basic: semanticSearch(getSearchSpecEmbedding(0), 0.5, 1, "COSINE")</li>
+ * <li>Basic: semanticSearch(getEmbeddingParameter(0), 0.5, 1, "COSINE")</li>
* <li>With a property restriction:
- * property1:semanticSearch(getSearchSpecEmbedding(0), 0.5, 1)</li>
- * <li>Hybrid: foo OR semanticSearch(getSearchSpecEmbedding(0), 0.5, 1)</li>
- * <li>Complex: (foo OR semanticSearch(getSearchSpecEmbedding(0), 0.5, 1)) AND bar</li>
+ * property1:semanticSearch(getEmbeddingParameter(0), 0.5, 1)</li>
+ * <li>Hybrid: foo OR semanticSearch(getEmbeddingParameter(0), 0.5, 1)</li>
+ * <li>Complex: (foo OR semanticSearch(getEmbeddingParameter(0), 0.5, 1)) AND bar</li>
* </ul>
*
- * <p>{@link Features#LIST_FILTER_TOKENIZE_FUNCTION}: This feature covers the
- * "tokenize" function in query expressions, which takes a string and treats the entire string
- * as plain text. This string is then segmented, normalized and stripped of punctuation-only
- * segments. The remaining tokens are then AND'd together. This function is useful for callers
- * who wish to provide user input, but want to ensure that that user input does not invoke any
- * query operators.
+ * <p>{@link Features#SEARCH_SPEC_SEARCH_STRING_PARAMETERS}: This feature covers the
+ * "getSearchStringParameter" function in query expressions, which substitutes the string
+ * provided at the same index in {@link SearchSpec.Builder#addSearchStringParameters} into the
+ * query as plain text. This string is then segmented, normalized and stripped of
+ * punctuation-only segments. The remaining tokens are then AND'd together. This function is
+ * useful for callers who wish to provide user input, but want to ensure that that user input
+ * does not invoke any query operators.
*
- * <p>Ex. `foo OR tokenize("bar OR baz.")`. The string "bar OR baz." will be segmented into
- * "bar", "OR", "baz", ".". Punctuation is removed and the segments are normalized to "bar",
- * "or", "baz". This query will be equivalent to `foo OR (bar AND or AND baz)`.
+ * <p>Usage: getSearchStringParameter({search_parameter_strings_index})
*
- * <p>Ex. `tokenize("\"bar\" OR \\baz")`. Quotation marks and escape characters must be escaped.
- * This query will be segmented into "\"", "bar", "\"", "OR", "\", "baz". Once stripped of
- * punctuation and normalized, this will be equivalent to the query `bar AND or AND baz`.
+ * <p>Ex. `foo OR getSearchStringParameter(0)` with {@link SearchSpec#getSearchStringParameters}
+ * returning {"bar OR baz."}. The string "bar OR baz." will be segmented into "bar", "OR",
+ * "baz", ".". Punctuation is removed and the segments are normalized to "bar", "or", "baz".
+ * This query will be equivalent to `foo OR (bar AND or AND baz)`.
*
* <p>The availability of each of these features can be checked by calling
* {@link Features#isFeatureSupported} with the desired feature.
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/EmbeddingVector.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/EmbeddingVector.java
index 1addca7..a691027 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/EmbeddingVector.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/EmbeddingVector.java
@@ -43,7 +43,7 @@
* <p>For more details on how embedding search works, check {@link AppSearchSession#search} and
* {@link SearchSpec.Builder#setRankingStrategy(String)}.
*
- * @see SearchSpec.Builder#addSearchEmbeddings
+ * @see SearchSpec.Builder#addEmbeddingParameters
* @see GenericDocument.Builder#setPropertyEmbedding
*/
@RequiresFeature(
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/FeatureConstants.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/FeatureConstants.java
index c89fb1e..5a54e55f 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/FeatureConstants.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/FeatureConstants.java
@@ -44,8 +44,5 @@
/** A feature constant for the "semanticSearch" function in {@link AppSearchSession#search}. */
public static final String EMBEDDING_SEARCH = "EMBEDDING_SEARCH";
- /** A feature constant for the "tokenize" function in {@link AppSearchSession#search}. */
- public static final String LIST_FILTER_TOKENIZE_FUNCTION = "TOKENIZE";
-
private FeatureConstants() {}
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
index e89dc99..ac1de38 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
@@ -120,15 +120,6 @@
String LIST_FILTER_HAS_PROPERTY_FUNCTION = FeatureConstants.LIST_FILTER_HAS_PROPERTY_FUNCTION;
/**
- * Feature for {@link #isFeatureSupported(String)}. This feature covers the use of the
- * "tokenize" function in query expressions.
- *
- * <p>For details on the "tokenize" function in the query language, see
- * {@link AppSearchSession#search}.
- */
- String LIST_FILTER_TOKENIZE_FUNCTION = "LIST_FILTER_TOKENIZE_FUNCTION";
-
- /**
* Feature for {@link #isFeatureSupported(String)}. This feature covers whether or not the
* AppSearch backend can store the descriptions returned by
* {@link AppSearchSchema#getDescription} and
@@ -172,6 +163,13 @@
String SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION = "SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION";
/**
+ * Feature for {@link #isFeatureSupported(String)}. This feature covers the support of the
+ * {@link SearchSpec.Builder#addSearchStringParameters} and
+ * {@link SearchSuggestionSpec.Builder#addSearchStringParameters} apis.
+ */
+ String SEARCH_SPEC_SEARCH_STRING_PARAMETERS = "SEARCH_SPEC_SEARCH_STRING_PARAMETERS";
+
+ /**
* Feature for {@link #isFeatureSupported(String)}. This feature covers
* {@link AppSearchSchema.StringPropertyConfig#JOINABLE_VALUE_TYPE_QUALIFIED_ID},
* {@link SearchSpec.Builder#setJoinSpec}, and all other join features.
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
index 1b43e15..5128307d 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
@@ -146,8 +146,8 @@
@Nullable private final String mSearchSourceLogTag;
@NonNull
- @Field(id = 20, getter = "getSearchEmbeddings")
- private final List<EmbeddingVector> mSearchEmbeddings;
+ @Field(id = 20, getter = "getEmbeddingParameters")
+ private final List<EmbeddingVector> mEmbeddingParameters;
@Field(id = 21, getter = "getDefaultEmbeddingSearchMetricType")
private final int mDefaultEmbeddingSearchMetricType;
@@ -156,6 +156,10 @@
@Field(id = 22, getter = "getInformationalRankingExpressions")
private final List<String> mInformationalRankingExpressions;
+ @NonNull
+ @Field(id = 23, getter = "getSearchStringParameters")
+ private final List<String> mSearchStringParameters;
+
/**
* Default number of documents per page.
*
@@ -364,9 +368,10 @@
@Param(id = 17) @NonNull String advancedRankingExpression,
@Param(id = 18) @NonNull List<String> enabledFeatures,
@Param(id = 19) @Nullable String searchSourceLogTag,
- @Param(id = 20) @Nullable List<EmbeddingVector> searchEmbeddings,
+ @Param(id = 20) @Nullable List<EmbeddingVector> embeddingParameters,
@Param(id = 21) int defaultEmbeddingSearchMetricType,
- @Param(id = 22) @Nullable List<String> informationalRankingExpressions
+ @Param(id = 22) @Nullable List<String> informationalRankingExpressions,
+ @Param(id = 23) @Nullable List<String> searchStringParameters
) {
mTermMatchType = termMatchType;
mSchemas = Collections.unmodifiableList(Preconditions.checkNotNull(schemas));
@@ -388,10 +393,10 @@
mEnabledFeatures = Collections.unmodifiableList(
Preconditions.checkNotNull(enabledFeatures));
mSearchSourceLogTag = searchSourceLogTag;
- if (searchEmbeddings != null) {
- mSearchEmbeddings = Collections.unmodifiableList(searchEmbeddings);
+ if (embeddingParameters != null) {
+ mEmbeddingParameters = Collections.unmodifiableList(embeddingParameters);
} else {
- mSearchEmbeddings = Collections.emptyList();
+ mEmbeddingParameters = Collections.emptyList();
}
mDefaultEmbeddingSearchMetricType = defaultEmbeddingSearchMetricType;
if (informationalRankingExpressions != null) {
@@ -400,6 +405,10 @@
} else {
mInformationalRankingExpressions = Collections.emptyList();
}
+ mSearchStringParameters =
+ (searchStringParameters != null)
+ ? Collections.unmodifiableList(searchStringParameters)
+ : Collections.emptyList();
}
@@ -673,12 +682,30 @@
}
/**
- * Returns the list of {@link EmbeddingVector} for embedding search.
+ * Returns the list of {@link EmbeddingVector} that can be referenced in the query through the
+ * "getEmbeddingParameter({index})" function.
+ *
+ * @see AppSearchSession#search
+ *
+ * @deprecated Use {@link #getEmbeddingParameters}.
*/
@NonNull
@FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+ @Deprecated
public List<EmbeddingVector> getSearchEmbeddings() {
- return mSearchEmbeddings;
+ return getEmbeddingParameters();
+ }
+
+ /**
+ * Returns the list of {@link EmbeddingVector} that can be referenced in the query through the
+ * "getEmbeddingParameter({index})" function.
+ *
+ * @see AppSearchSession#search
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+ public List<EmbeddingVector> getEmbeddingParameters() {
+ return mEmbeddingParameters;
}
/**
@@ -704,6 +731,18 @@
}
/**
+ * Returns the list of String parameters that can be referenced in the query through the
+ * "getSearchStringParameter({index})" function.
+ *
+ * @see AppSearchSession#search
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ public List<String> getSearchStringParameters() {
+ return mSearchStringParameters;
+ }
+
+ /**
* Returns whether the NUMERIC_SEARCH feature is enabled.
*/
public boolean isNumericSearchEnabled() {
@@ -734,21 +773,18 @@
/**
* Returns whether the embedding search feature is enabled.
+ *
+ * @deprecated AppSearch no longer requires clients to explicitly enable embedding search
+ * because {@link Builder#addEmbeddingParameters} is already guarded by {@link
+ * androidx.appsearch.app.Features#isFeatureSupported}.
*/
+ @Deprecated
@FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
public boolean isEmbeddingSearchEnabled() {
return mEnabledFeatures.contains(FeatureConstants.EMBEDDING_SEARCH);
}
/**
- * Returns whether the LIST_FILTER_TOKENIZE_FUNCTION feature is enabled.
- */
- @FlaggedApi(Flags.FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION)
- public boolean isListFilterTokenizeFunctionEnabled() {
- return mEnabledFeatures.contains(FeatureConstants.LIST_FILTER_TOKENIZE_FUNCTION);
- }
-
- /**
* Get the list of enabled features that the caller is intending to use in this search call.
*
* @return the set of {@link Features} enabled in this {@link SearchSpec} Entry.
@@ -776,7 +812,8 @@
private ArraySet<String> mEnabledFeatures = new ArraySet<>();
private Bundle mProjectionTypePropertyMasks = new Bundle();
private Bundle mTypePropertyWeights = new Bundle();
- private List<EmbeddingVector> mSearchEmbeddings = new ArrayList<>();
+ private List<EmbeddingVector> mEmbeddingParameters = new ArrayList<>();
+ private List<String> mSearchStringParameters = new ArrayList<>();
private int mResultCountPerPage = DEFAULT_NUM_PER_PAGE;
@TermMatch private int mTermMatchType = TERM_MATCH_PREFIX;
@@ -818,7 +855,8 @@
searchSpec.getPropertyWeights().entrySet()) {
setPropertyWeights(entry.getKey(), entry.getValue());
}
- mSearchEmbeddings = new ArrayList<>(searchSpec.getSearchEmbeddings());
+ mEmbeddingParameters = new ArrayList<>(searchSpec.getEmbeddingParameters());
+ mSearchStringParameters = new ArrayList<>(searchSpec.getSearchStringParameters());
mResultCountPerPage = searchSpec.getResultCountPerPage();
mTermMatchType = searchSpec.getTermMatch();
mDefaultEmbeddingSearchMetricType = searchSpec.getDefaultEmbeddingSearchMetricType();
@@ -1232,7 +1270,7 @@
* current document being scored. Property weights come from what's specified in
* {@link SearchSpec}. After normalizing, each provided weight will be divided by the
* maximum weight, so that each of them will be <= 1.
- * <li>this.matchedSemanticScores(getSearchSpecEmbedding({embedding_index}), {metric})
+ * <li>this.matchedSemanticScores(getEmbeddingParameter({embedding_index}), {metric})
* <p>Returns a list of the matched similarity scores from "semanticSearch" in the query
* expression (see also {@link AppSearchSession#search}) based on embedding_index and
* metric. If metric is omitted, it defaults to the metric specified in
@@ -1241,9 +1279,9 @@
* function will return an empty list. If multiple "semanticSearch"s are called for
* the same embedding_index and metric, this function will return a list of their
* merged scores.
- * <p>Example: `this.matchedSemanticScores(getSearchSpecEmbedding(0), "COSINE")` will
+ * <p>Example: `this.matchedSemanticScores(getEmbeddingParameter(0), "COSINE")` will
* return a list of matched scores within the range of [0.5, 1], if
- * `semanticSearch(getSearchSpecEmbedding(0), 0.5, 1, "COSINE")` is called in the
+ * `semanticSearch(getEmbeddingParameter(0), 0.5, 1, "COSINE")` is called in the
* query expression.
* </ul>
*
@@ -1870,13 +1908,14 @@
return setPropertyWeightPaths(factory.getSchemaName(), propertyPathWeights);
}
// @exportToFramework:endStrip()
-
/**
* Adds an embedding search to {@link SearchSpec} Entry, which will be referred in the
* query expression and the ranking expression for embedding search.
*
* @see AppSearchSession#search
* @see SearchSpec.Builder#setRankingStrategy(String)
+ *
+ * @deprecated Use {@link #addEmbeddingParameters}
*/
@CanIgnoreReturnValue
@NonNull
@@ -1884,10 +1923,30 @@
enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
name = Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG)
@FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+ @Deprecated
public Builder addSearchEmbeddings(@NonNull EmbeddingVector... searchEmbeddings) {
- Preconditions.checkNotNull(searchEmbeddings);
- resetIfBuilt();
- return addSearchEmbeddings(Arrays.asList(searchEmbeddings));
+ return addEmbeddingParameters(searchEmbeddings);
+ }
+
+ /**
+ * Adds an embedding search to {@link SearchSpec} Entry, which will be referred in the
+ * query expression and the ranking expression for embedding search.
+ *
+ * @see AppSearchSession#search
+ * @see SearchSpec.Builder#setRankingStrategy(String)
+ *
+ * @deprecated Use {@link #addEmbeddingParameters}
+ */
+ @CanIgnoreReturnValue
+ @NonNull
+ @RequiresFeature(
+ enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+ name = Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+ @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+ @Deprecated
+ public Builder addSearchEmbeddings(
+ @NonNull Collection<EmbeddingVector> searchEmbeddings) {
+ return addEmbeddingParameters(searchEmbeddings);
}
/**
@@ -1903,11 +1962,30 @@
enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
name = Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG)
@FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
- public Builder addSearchEmbeddings(
+ public Builder addEmbeddingParameters(@NonNull EmbeddingVector... searchEmbeddings) {
+ Preconditions.checkNotNull(searchEmbeddings);
+ resetIfBuilt();
+ return addEmbeddingParameters(Arrays.asList(searchEmbeddings));
+ }
+
+ /**
+ * Adds an embedding search to {@link SearchSpec} Entry, which will be referred in the
+ * query expression and the ranking expression for embedding search.
+ *
+ * @see AppSearchSession#search
+ * @see SearchSpec.Builder#setRankingStrategy(String)
+ */
+ @CanIgnoreReturnValue
+ @NonNull
+ @RequiresFeature(
+ enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+ name = Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+ @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+ public Builder addEmbeddingParameters(
@NonNull Collection<EmbeddingVector> searchEmbeddings) {
Preconditions.checkNotNull(searchEmbeddings);
resetIfBuilt();
- mSearchEmbeddings.addAll(searchEmbeddings);
+ mEmbeddingParameters.addAll(searchEmbeddings);
return this;
}
@@ -1938,6 +2016,43 @@
}
/**
+ * Adds Strings to the list of String parameters that can be referenced in the query through
+ * the "getSearchStringParameter({index})" function.
+ *
+ * @see AppSearchSession#search
+ */
+ @CanIgnoreReturnValue
+ @NonNull
+ @RequiresFeature(
+ enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+ name = Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ public Builder addSearchStringParameters(@NonNull String... searchStringParameters) {
+ Preconditions.checkNotNull(searchStringParameters);
+ resetIfBuilt();
+ return addSearchStringParameters(Arrays.asList(searchStringParameters));
+ }
+
+ /**
+ * Adds Strings to the list of String parameters that can be referenced in the query through
+ * the "getSearchStringParameter({index})" function.
+ *
+ * @see AppSearchSession#search
+ */
+ @CanIgnoreReturnValue
+ @NonNull
+ @RequiresFeature(
+ enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+ name = Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ public Builder addSearchStringParameters(@NonNull List<String> searchStringParameters) {
+ Preconditions.checkNotNull(searchStringParameters);
+ resetIfBuilt();
+ mSearchStringParameters.addAll(searchStringParameters);
+ return this;
+ }
+
+ /**
* Sets the NUMERIC_SEARCH feature as enabled/disabled according to the enabled parameter.
*
* @param enabled Enables the feature if true, otherwise disables it.
@@ -2045,11 +2160,16 @@
* {@link AppSearchSession#search} for more details about the function.
*
* @param enabled Enables the feature if true, otherwise disables it
+ *
+ * @deprecated AppSearch no longer requires clients to explicitly enable embedding search
+ * because {@link #addEmbeddingParameters} is already guarded by {@link
+ * androidx.appsearch.app.Features#isFeatureSupported}.
*/
@CanIgnoreReturnValue
@RequiresFeature(
enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
name = Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+ @Deprecated
@NonNull
@FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
public Builder setEmbeddingSearchEnabled(boolean enabled) {
@@ -2058,26 +2178,6 @@
}
/**
- * Sets the LIST_FILTER_TOKENIZE_FUNCTION feature as enabled/disabled according to
- * the enabled parameter.
- *
- * @param enabled Enables the feature if true, otherwise disables it
- *
- * <p>If disabled, disallows the use of the "tokenize" function. See
- * {@link AppSearchSession#search} for more details about the function.
- */
- @CanIgnoreReturnValue
- @RequiresFeature(
- enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
- name = Features.LIST_FILTER_TOKENIZE_FUNCTION)
- @NonNull
- @FlaggedApi(Flags.FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION)
- public Builder setListFilterTokenizeFunctionEnabled(boolean enabled) {
- modifyEnabledFeature(FeatureConstants.LIST_FILTER_TOKENIZE_FUNCTION, enabled);
- return this;
- }
-
- /**
* Constructs a new {@link SearchSpec} from the contents of this builder.
*
* @throws IllegalArgumentException if property weights are provided with a
@@ -2119,8 +2219,9 @@
mRankingStrategy, mOrder, mSnippetCount, mSnippetCountPerProperty,
mMaxSnippetSize, mProjectionTypePropertyMasks, mGroupingTypeFlags,
mGroupingLimit, mTypePropertyWeights, mJoinSpec, mAdvancedRankingExpression,
- new ArrayList<>(mEnabledFeatures), mSearchSourceLogTag, mSearchEmbeddings,
- mDefaultEmbeddingSearchMetricType, mInformationalRankingExpressions);
+ new ArrayList<>(mEnabledFeatures), mSearchSourceLogTag, mEmbeddingParameters,
+ mDefaultEmbeddingSearchMetricType, mInformationalRankingExpressions,
+ mSearchStringParameters);
}
private void resetIfBuilt() {
@@ -2131,9 +2232,10 @@
mPackageNames = new ArrayList<>(mPackageNames);
mProjectionTypePropertyMasks = BundleUtil.deepCopy(mProjectionTypePropertyMasks);
mTypePropertyWeights = BundleUtil.deepCopy(mTypePropertyWeights);
- mSearchEmbeddings = new ArrayList<>(mSearchEmbeddings);
+ mEmbeddingParameters = new ArrayList<>(mEmbeddingParameters);
mInformationalRankingExpressions = new ArrayList<>(
mInformationalRankingExpressions);
+ mSearchStringParameters = new ArrayList<>(mSearchStringParameters);
mBuilt = false;
}
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSuggestionSpec.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSuggestionSpec.java
index f2db304..7a7bfbb 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSuggestionSpec.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSuggestionSpec.java
@@ -24,6 +24,7 @@
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresFeature;
import androidx.annotation.RestrictTo;
import androidx.appsearch.annotation.CanIgnoreReturnValue;
@@ -62,23 +63,37 @@
@FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2)
@NonNull public static final Parcelable.Creator<SearchSuggestionSpec> CREATOR =
new SearchSuggestionSpecCreator();
+
+ @NonNull
@Field(id = 1, getter = "getFilterNamespaces")
private final List<String> mFilterNamespaces;
+
+ @NonNull
@Field(id = 2, getter = "getFilterSchemas")
private final List<String> mFilterSchemas;
+
// Maps are not supported by SafeParcelable fields, using Bundle instead. Here the key is
// schema type and value is a list of target property paths in that schema to search over.
+ @NonNull
@Field(id = 3)
final Bundle mFilterProperties;
+
// Maps are not supported by SafeParcelable fields, using Bundle instead. Here the key is
// namespace and value is a list of target document ids in that namespace to search over.
+ @NonNull
@Field(id = 4)
final Bundle mFilterDocumentIds;
+
@Field(id = 5, getter = "getRankingStrategy")
private final int mRankingStrategy;
+
@Field(id = 6, getter = "getMaximumResultCount")
private final int mMaximumResultCount;
+ @NonNull
+ @Field(id = 7, getter = "getSearchStringParameters")
+ private final List<String> mSearchStringParameters;
+
/** @exportToFramework:hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Constructor
@@ -88,7 +103,8 @@
@Param(id = 3) @NonNull Bundle filterProperties,
@Param(id = 4) @NonNull Bundle filterDocumentIds,
@Param(id = 5) @SuggestionRankingStrategy int rankingStrategy,
- @Param(id = 6) int maximumResultCount) {
+ @Param(id = 6) int maximumResultCount,
+ @Param(id = 7) @Nullable List<String> searchStringParameters) {
Preconditions.checkArgument(maximumResultCount >= 1,
"MaximumResultCount must be positive.");
mFilterNamespaces = Preconditions.checkNotNull(filterNamespaces);
@@ -97,6 +113,10 @@
mFilterDocumentIds = Preconditions.checkNotNull(filterDocumentIds);
mRankingStrategy = rankingStrategy;
mMaximumResultCount = maximumResultCount;
+ mSearchStringParameters =
+ (searchStringParameters != null)
+ ? Collections.unmodifiableList(searchStringParameters)
+ : Collections.emptyList();
}
/**
@@ -228,6 +248,18 @@
return documentIdsMap;
}
+ /**
+ * Returns the list of String parameters that can be referenced in the query through the
+ * "getSearchStringParameter({index})" function.
+ *
+ * @see AppSearchSession#search
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ public List<String> getSearchStringParameters() {
+ return mSearchStringParameters;
+ }
+
/** Builder for {@link SearchSuggestionSpec objects}. */
public static final class Builder {
private ArrayList<String> mNamespaces = new ArrayList<>();
@@ -237,6 +269,7 @@
private final int mTotalResultCount;
@SuggestionRankingStrategy private int mRankingStrategy =
SUGGESTION_RANKING_STRATEGY_DOCUMENT_COUNT;
+ private List<String> mSearchStringParameters = new ArrayList<>();
private boolean mBuilt = false;
/**
@@ -557,6 +590,43 @@
return this;
}
+ /**
+ * Adds Strings to the list of String parameters that can be referenced in the query through
+ * the "getSearchStringParameter({index})" function.
+ *
+ * @see AppSearchSession#search
+ */
+ @CanIgnoreReturnValue
+ @NonNull
+ @RequiresFeature(
+ enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+ name = Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ public Builder addSearchStringParameters(@NonNull String... searchStringParameters) {
+ Preconditions.checkNotNull(searchStringParameters);
+ resetIfBuilt();
+ return addSearchStringParameters(Arrays.asList(searchStringParameters));
+ }
+
+ /**
+ * Adds Strings to the list of String parameters that can be referenced in the query through
+ * the "getSearchStringParameter({index})" function.
+ *
+ * @see AppSearchSession#search
+ */
+ @CanIgnoreReturnValue
+ @NonNull
+ @RequiresFeature(
+ enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+ name = Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+ public Builder addSearchStringParameters(@NonNull List<String> searchStringParameters) {
+ Preconditions.checkNotNull(searchStringParameters);
+ resetIfBuilt();
+ mSearchStringParameters.addAll(searchStringParameters);
+ return this;
+ }
+
/** Constructs a new {@link SearchSpec} from the contents of this builder. */
@NonNull
public SearchSuggestionSpec build() {
@@ -587,7 +657,8 @@
mTypePropertyFilters,
mDocumentIds,
mRankingStrategy,
- mTotalResultCount);
+ mTotalResultCount,
+ mSearchStringParameters);
}
private void resetIfBuilt() {
@@ -596,6 +667,7 @@
mSchemas = new ArrayList<>(mSchemas);
mTypePropertyFilters = BundleUtil.deepCopy(mTypePropertyFilters);
mDocumentIds = BundleUtil.deepCopy(mDocumentIds);
+ mSearchStringParameters = new ArrayList<>(mSearchStringParameters);
mBuilt = false;
}
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java b/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
index c6cc193..eaf90b6 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
@@ -54,11 +54,6 @@
public static final String FLAG_ENABLE_LIST_FILTER_HAS_PROPERTY_FUNCTION =
FLAG_PREFIX + "enable_list_filter_has_property_function";
- /** Enable the "tokenize" function in list filter query expressions. */
- public static final String FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION =
- FLAG_PREFIX + "enable_list_filter_tokenize_function";
-
-
/** Enable Schema Type Grouping related features. */
public static final String FLAG_ENABLE_GROUPING_TYPE_PER_SCHEMA =
FLAG_PREFIX + "enable_grouping_type_per_schema";
@@ -79,6 +74,14 @@
public static final String FLAG_ENABLE_SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG =
FLAG_PREFIX + "enable_search_spec_set_search_source_log_tag";
+ /**
+ * Enable {@link androidx.appsearch.app.SearchSpec.Builder#addSearchStringParameters} and
+ * {@link androidx.appsearch.app.SearchSuggestionSpec.Builder#addSearchStringParameters}
+ * methods.
+ */
+ public static final String FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS =
+ FLAG_PREFIX + "enable_search_spec_search_spec_strings";
+
/** Enable addTakenActions API in PutDocumentsRequest. */
public static final String FLAG_ENABLE_PUT_DOCUMENTS_REQUEST_ADD_TAKEN_ACTIONS =
FLAG_PREFIX + "enable_put_documents_request_add_taken_actions";
@@ -234,8 +237,8 @@
return true;
}
- /** Whether the "tokenize" function in list filter query expressions should be enabled. */
- public static boolean enableListFilterTokenizeFunction() {
+ /** Whether the search parameter APIs should be enabled. */
+ public static boolean enableSearchSpecSearchStringParameters() {
return true;
}
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/MetricCaptureTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/MetricCaptureTest.kt
index 542f2ba..a1176a6 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/MetricCaptureTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/MetricCaptureTest.kt
@@ -44,9 +44,22 @@
}
@Test
+ fun cpuEventCounterCapture_outputName() {
+ CpuEventCounter().use {
+ assertEquals(
+ listOf("instructions", "cpuCycles"),
+ CpuEventCounterCapture(
+ it,
+ listOf(CpuEventCounter.Event.Instructions, CpuEventCounter.Event.CpuCycles)
+ )
+ .names
+ )
+ }
+ }
+
+ @Test
fun cpuEventCounterCapture_multi() {
try {
-
// skip test if need root, or event fails to enable
CpuEventCounter.forceEnable()?.let { errorMessage -> assumeTrue(errorMessage, false) }
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/CpuEventCounter.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/CpuEventCounter.kt
index 8b29a81..725a125 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/CpuEventCounter.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/CpuEventCounter.kt
@@ -20,6 +20,7 @@
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import java.io.Closeable
+import java.util.Locale
/**
* Exposes CPU counters from perf_event_open based on libs/utils/src/Profiler.cpp from
@@ -87,6 +88,8 @@
val flag: Int
inline get() = 1 shl id
+
+ val outputName = name.replaceFirstChar { it.lowercase(Locale.US) }
}
/**
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
index 1432196..417495f 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
@@ -146,7 +146,7 @@
internal class CpuEventCounterCapture(
private val cpuEventCounter: CpuEventCounter,
private val events: List<CpuEventCounter.Event>
-) : MetricCapture(events.map { it.name }) {
+) : MetricCapture(events.map { it.outputName }) {
constructor(
cpuEventCounter: CpuEventCounter,
mask: Int
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricNameUtils.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricNameUtils.kt
index f4a8ef4..2a26600 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricNameUtils.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricNameUtils.kt
@@ -32,7 +32,7 @@
* This functionality is a stopgap as we migrate to actually using JSON in CI.
*/
internal fun String.toOutputMetricName() =
- this.replaceFirstChar { it.lowercase(Locale.getDefault()) }
+ this.replaceFirstChar { it.lowercase(Locale.US) }
.toSnakeCase()
.replace(Regex("_ns$"), "_nanos")
.replace(Regex("_ms$"), "_millis")
diff --git a/buildSrc-tests/src/test/java/androidx/build/LibraryVersionsServiceTest.kt b/buildSrc-tests/src/test/java/androidx/build/LibraryVersionsServiceTest.kt
index 8dc3df7..2de82d0 100644
--- a/buildSrc-tests/src/test/java/androidx/build/LibraryVersionsServiceTest.kt
+++ b/buildSrc-tests/src/test/java/androidx/build/LibraryVersionsServiceTest.kt
@@ -29,155 +29,136 @@
@RunWith(JUnit4::class)
class LibraryVersionsServiceTest {
- @get:Rule
- val tempDir = TemporaryFolder()
+ @get:Rule val tempDir = TemporaryFolder()
@Test
fun basic() {
- val service = createLibraryVersionsService(
- """
+ val service =
+ createLibraryVersionsService(
+ """
[versions]
V1 = "1.2.3"
[groups]
G1 = { group = "g.g1", atomicGroupVersion = "versions.V1" }
G2 = { group = "g.g2"}
- """.trimIndent()
- )
- assertThat(
- service.libraryGroups["G1"]
- ).isEqualTo(
- LibraryGroup(
- group = "g.g1", atomicGroupVersion = Version("1.2.3")
+ """
+ .trimIndent()
)
- )
- assertThat(
- service.libraryGroups["G2"]
- ).isEqualTo(
- LibraryGroup(
- group = "g.g2", atomicGroupVersion = null
- )
- )
+ assertThat(service.libraryGroups["G1"])
+ .isEqualTo(LibraryGroup(group = "g.g1", atomicGroupVersion = Version("1.2.3")))
+ assertThat(service.libraryGroups["G2"])
+ .isEqualTo(LibraryGroup(group = "g.g2", atomicGroupVersion = null))
}
@Test
fun invalidToml() {
- val service = createLibraryVersionsService(
- """
+ val service =
+ createLibraryVersionsService(
+ """
[versions]
V1 = "1.2.3"
[groups]
G1 = { group = "g.g1", atomicGroupVersion = "versions.V1" }
G1 = { group = "g.g1"}
- """.trimIndent()
- )
- assertThrows<Exception> {
- service.libraryGroups["G1"]
- }.hasMessageThat().contains(
- "libraryversions.toml:line 5, column 1: G1 previously defined at line 4, column 1"
- )
+ """
+ .trimIndent()
+ )
+ assertThrows<Exception> { service.libraryGroups["G1"] }
+ .hasMessageThat()
+ .contains(
+ "libraryversions.toml:line 5, column 1: G1 previously defined at line 4, column 1"
+ )
}
@Test
fun missingVersionReference() {
- val service = createLibraryVersionsService(
- """
+ val service =
+ createLibraryVersionsService(
+ """
[versions]
V1 = "1.2.3"
[groups]
G1 = { group = "g.g1", atomicGroupVersion = "versions.doesNotExist" }
- """.trimIndent()
- )
- val result = runCatching {
- service.libraryGroups["G1"]
- }
- assertThat(
- result.exceptionOrNull()
- ).hasMessageThat().contains(
- "Group entry g.g1 specifies doesNotExist, but such version doesn't exist"
- )
+ """
+ .trimIndent()
+ )
+ val result = runCatching { service.libraryGroups["G1"] }
+ assertThat(result.exceptionOrNull())
+ .hasMessageThat()
+ .contains("Group entry g.g1 specifies doesNotExist, but such version doesn't exist")
}
@Test
fun malformedVersionReference() {
- val service = createLibraryVersionsService(
- """
+ val service =
+ createLibraryVersionsService(
+ """
[versions]
V1 = "1.2.3"
[groups]
G1 = { group = "g.g1", atomicGroupVersion = "v1" }
- """.trimIndent()
- )
- val result = runCatching {
- service.libraryGroups["G1"]
- }
- assertThat(
- result.exceptionOrNull()
- ).hasMessageThat().contains(
- "Group entry atomicGroupVersion is expected to start with versions"
- )
+ """
+ .trimIndent()
+ )
+ val result = runCatching { service.libraryGroups["G1"] }
+ assertThat(result.exceptionOrNull())
+ .hasMessageThat()
+ .contains("Group entry atomicGroupVersion is expected to start with versions")
}
@Test
fun overrideInclude() {
- val service = createLibraryVersionsService(
- """
+ val service =
+ createLibraryVersionsService(
+ """
[versions]
V1 = "1.2.3"
[groups]
G1 = { group = "g.g1", atomicGroupVersion = "versions.V1", overrideInclude = [ ":otherGroup:subproject" ]}
"""
- )
- assertThat(
- service.overrideLibraryGroupsByProjectPath.get(":otherGroup:subproject")
- ).isEqualTo(
- LibraryGroup(
- group = "g.g1", atomicGroupVersion = Version("1.2.3")
)
- )
- assertThat(
- service.overrideLibraryGroupsByProjectPath.get(":normalGroup:subproject")
- ).isEqualTo(null)
+ assertThat(service.overrideLibraryGroupsByProjectPath.get(":otherGroup:subproject"))
+ .isEqualTo(LibraryGroup(group = "g.g1", atomicGroupVersion = Version("1.2.3")))
+ assertThat(service.overrideLibraryGroupsByProjectPath.get(":normalGroup:subproject"))
+ .isEqualTo(null)
}
@Test
fun duplicateGroupIdsWithoutOverrideInclude() {
- val service = createLibraryVersionsService(
- """
+ val service =
+ createLibraryVersionsService(
+ """
[versions]
V1 = "1.2.3"
[groups]
G1 = { group = "g.g1", atomicGroupVersion = "versions.V1" }
G2 = { group = "g.g1", atomicGroupVersion = "versions.V1" }
"""
- )
+ )
- assertThrows<Exception> {
- service.libraryGroupsByGroupId["g.g1"]
- }.hasMessageThat().contains(
- "Duplicate library group g.g1 defined in G2 does not set overrideInclude. " +
- "Declarations beyond the first can only have an effect if they set overrideInclude"
- )
+ assertThrows<Exception> { service.libraryGroupsByGroupId["g.g1"] }
+ .hasMessageThat()
+ .contains(
+ "Duplicate library group g.g1 defined in G2 does not set overrideInclude. " +
+ "Declarations beyond the first can only have an effect if they set overrideInclude"
+ )
}
@Test
fun duplicateGroupIdsWithOverrideInclude() {
- val service = createLibraryVersionsService(
- """
+ val service =
+ createLibraryVersionsService(
+ """
[versions]
V1 = "1.2.3"
[groups]
G1 = { group = "g.g1", atomicGroupVersion = "versions.V1" }
G2 = { group = "g.g1", atomicGroupVersion = "versions.V1", overrideInclude = ["sample"] }
"""
- )
-
- assertThat(
- service.libraryGroupsByGroupId["g.g1"]
- ).isEqualTo(
- LibraryGroup(
- group = "g.g1", atomicGroupVersion = Version("1.2.3")
)
- )
+
+ assertThat(service.libraryGroupsByGroupId["g.g1"])
+ .isEqualTo(LibraryGroup(group = "g.g1", atomicGroupVersion = Version("1.2.3")))
}
private fun runAndroidExtensionTest(
@@ -188,13 +169,9 @@
) {
listOf(false, true).forEach { useKmpVersions ->
val rootProjectDir = tempDir.newFolder()
- val rootProject = ProjectBuilder.builder().withProjectDir(
- rootProjectDir
- ).build()
- val subject = ProjectBuilder.builder()
- .withParent(rootProject)
- .withName(projectPath)
- .build()
+ val rootProject = ProjectBuilder.builder().withProjectDir(rootProjectDir).build()
+ val subject =
+ ProjectBuilder.builder().withParent(rootProject).withName(projectPath).build()
// create the service before extensions are created so that they'll use the test service
// we've created.
createLibraryVersionsService(
@@ -204,8 +181,8 @@
// needed for AndroidXExtension initialization
rootProject.setSupportRootFolder(rootProjectDir)
// create androidx extensions
- val extension = subject.extensions
- .create<AndroidXExtension>(AndroidXImplPlugin.EXTENSION_NAME)
+ val extension =
+ subject.extensions.create<AndroidXExtension>(AndroidXImplPlugin.EXTENSION_NAME)
if (useKmpVersions) {
validateWithKmp(extension)
} else {
@@ -217,24 +194,16 @@
private fun createLibraryVersionsService(
tomlFileContents: String,
tomlFileName: String = "libraryversions.toml",
- composeCustomVersion: String? = null,
- composeCustomGroup: String? = null,
project: Project = ProjectBuilder.builder().withProjectDir(tempDir.newFolder()).build()
): LibraryVersionsService {
- val serviceProvider = project.gradle.sharedServices.registerIfAbsent(
- "libraryVersionsService", LibraryVersionsService::class.java
- ) { spec ->
- spec.parameters.tomlFileContents = project.provider {
- tomlFileContents
+ val serviceProvider =
+ project.gradle.sharedServices.registerIfAbsent(
+ "libraryVersionsService",
+ LibraryVersionsService::class.java
+ ) { spec ->
+ spec.parameters.tomlFileContents = project.provider { tomlFileContents }
+ spec.parameters.tomlFileName = tomlFileName
}
- spec.parameters.tomlFileName = tomlFileName
- spec.parameters.composeCustomVersion = project.provider {
- composeCustomVersion
- }
- spec.parameters.composeCustomGroup = project.provider {
- composeCustomGroup
- }
- }
return serviceProvider.get()
}
}
diff --git a/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java b/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
index bf7a859..9781b2e 100644
--- a/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
+++ b/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
@@ -45,7 +45,6 @@
public String kotlinVersion;
public String path;
public String sha;
- public String groupZipPath;
public String projectZipPath;
public Boolean groupIdRequiresSameVersion;
public ArrayList<Dependency> dependencies;
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
index 93fa12e..57fc5cc 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
@@ -17,7 +17,6 @@
package androidx.build
import androidx.build.dependencies.KOTLIN_NATIVE_VERSION
-import com.android.build.api.dsl.CommonExtension
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.AppPlugin
import com.android.build.gradle.LibraryPlugin
@@ -46,10 +45,6 @@
when (plugin) {
is AppPlugin,
is LibraryPlugin -> {
- val commonExtension =
- project.extensions.findByType(CommonExtension::class.java)
- ?: throw Exception("Failed to find Android extension")
- commonExtension.defaultConfig.minSdk = 21
project.configureAndroidCommonOptions()
}
is KotlinBasePluginWrapper -> {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
index 6b043c7..8a6dbd1 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -36,13 +36,12 @@
@JvmField val AllLibraryGroups: List<LibraryGroup>
- val libraryGroupsByGroupId: Map<String, LibraryGroup>
- val overrideLibraryGroupsByProjectPath: Map<String, LibraryGroup>
+ private val libraryGroupsByGroupId: Map<String, LibraryGroup>
+ private val overrideLibraryGroupsByProjectPath: Map<String, LibraryGroup>
val mavenGroup: LibraryGroup?
- val listProjectsService: Provider<ListProjectsService>
-
+ private val listProjectsService: Provider<ListProjectsService>
private val versionService: LibraryVersionsService
val deviceTests = DeviceTests.register(project.extensions)
@@ -83,6 +82,24 @@
kotlinTestTarget.set(kotlinTarget)
}
+ /**
+ * Map of maven coordinates (e.g. "androidx.core:core") to a Gradle project path (e.g.
+ * ":core:core")
+ */
+ val mavenCoordinatesToProjectPathMap: Map<String, String> by lazy {
+ val newProjectMap: MutableMap<String, String> = mutableMapOf()
+ listProjectsService.get().allPossibleProjects.forEach {
+ val group =
+ overrideLibraryGroupsByProjectPath[it.gradlePath]
+ ?: getLibraryGroupFromProjectPath(it.gradlePath, null)
+ if (group != null) {
+ newProjectMap["${group.group}:${substringAfterLastColon(it.gradlePath)}"] =
+ it.gradlePath
+ }
+ }
+ newProjectMap
+ }
+
var name: Property<String?> = project.objects.property(String::class.java)
fun setName(newName: String) {
@@ -139,6 +156,11 @@
return projectPath.substring(0, lastColonIndex)
}
+ private fun substringAfterLastColon(projectPath: String): String {
+ val lastColonIndex = projectPath.lastIndexOf(":")
+ return projectPath.substring(lastColonIndex + 1)
+ }
+
// gets the library group from the project path, including special cases
private fun getLibraryGroupFromProjectPath(
projectPath: String,
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 31fcd62..eb2b57c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -66,7 +66,6 @@
import java.io.File
import java.time.Duration
import java.util.Locale
-import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
@@ -127,7 +126,7 @@
abstract class AndroidXImplPlugin
@Inject
constructor(private val componentFactory: SoftwareComponentFactory) : Plugin<Project> {
- @get:javax.inject.Inject abstract val registry: BuildEventsListenerRegistry
+ @get:Inject abstract val registry: BuildEventsListenerRegistry
override fun apply(project: Project) {
if (project.isRoot)
@@ -226,6 +225,7 @@
project.workaroundPrebuiltTakingPrecedenceOverProject()
project.configureSamplesProject()
+ project.configureMaxDepVersions(androidXExtension)
}
private fun Project.registerProjectOrArtifact() {
@@ -625,8 +625,6 @@
}
project.configureJavaCompilationWarnings(androidXExtension)
-
- project.addToProjectMap(androidXExtension)
}
private fun configureWithKotlinMultiplatformAndroidPlugin(
@@ -663,7 +661,6 @@
}
}
- project.addToProjectMap(androidXExtension)
project.afterEvaluate {
project.addToBuildOnServer("assembleAndroidMain")
project.addToBuildOnServer("lint")
@@ -899,8 +896,6 @@
project.setUpCheckDocsTask(androidXExtension)
- project.addToProjectMap(androidXExtension)
-
project.buildOnServerDependsOnAssembleRelease()
project.buildOnServerDependsOnLint()
}
@@ -991,8 +986,6 @@
}
}
}
-
- project.addToProjectMap(androidXExtension)
}
private fun Project.configureProjectStructureValidation(androidXExtension: AndroidXExtension) {
@@ -1404,9 +1397,6 @@
}
}
-private const val PROJECTS_MAP_KEY = "projects"
-private const val ACCESSED_PROJECTS_MAP_KEY = "accessedProjectsMap"
-
/** Returns whether the configuration is used for testing. */
private fun Configuration.isTest(): Boolean = name.lowercase().contains("test")
@@ -1425,30 +1415,6 @@
}
}
-private fun Project.addToProjectMap(androidXExtension: AndroidXExtension) {
- // TODO(alanv): Move this out of afterEvaluate
- afterEvaluate {
- if (androidXExtension.shouldRelease()) {
- val group = androidXExtension.mavenGroup?.group
- if (group != null) {
- val module = "$group:$name"
-
- if (project.rootProject.extra.has(ACCESSED_PROJECTS_MAP_KEY)) {
- throw GradleException(
- "Attempted to add $project to project map after " +
- "the contents of the map were accessed"
- )
- }
- @Suppress("UNCHECKED_CAST")
- val projectModules =
- project.rootProject.extra.get(PROJECTS_MAP_KEY)
- as ConcurrentHashMap<String, String>
- projectModules[module] = path
- }
- }
- }
-}
-
val Project.androidExtension: AndroidComponentsExtension<*, *, *>
get() =
extensions.findByType<LibraryAndroidComponentsExtension>()
@@ -1464,12 +1430,6 @@
val Project.androidXExtension: AndroidXExtension
get() = extensions.getByType()
-@Suppress("UNCHECKED_CAST")
-fun Project.getProjectsMap(): ConcurrentHashMap<String, String> {
- project.rootProject.extra.set(ACCESSED_PROJECTS_MAP_KEY, true)
- return rootProject.extra.get(PROJECTS_MAP_KEY) as ConcurrentHashMap<String, String>
-}
-
/**
* Configures all non-Studio tasks in a project (see b/153193718 for background) to time out after
* [TASK_TIMEOUT_MINUTES].
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
index 1d6f63f..ed28b5d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
@@ -35,7 +35,6 @@
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
-import org.gradle.api.artifacts.component.ModuleComponentSelector
import org.gradle.api.file.RelativePath
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.bundling.Zip
@@ -125,47 +124,6 @@
AffectedModuleDetector.configure(gradle, this)
registerOwnersServiceTasks()
-
- // If useMaxDepVersions is set, iterate through all the project and substitute any androidx
- // artifact dependency with the local tip of tree version of the library.
- if (project.usingMaxDepVersions()) {
- // This requires evaluating all sub-projects to create the module:project map
- // and project dependencies.
- allprojects { project2 ->
- // evaluationDependsOnChildren isn't transitive so we must call it on each project
- project2.evaluationDependsOnChildren()
- }
- val projectModules = getProjectsMap()
- subprojects { subproject ->
- // TODO(153485458) remove most of these exceptions
- if (
- !subproject.name.contains("hilt") &&
- subproject.name != "docs-public" &&
- subproject.name != "docs-tip-of-tree" &&
- subproject.name != "camera-testapp-timing" &&
- subproject.name != "room-testapp"
- ) {
- subproject.configurations.configureEach { configuration ->
- configuration.resolutionStrategy.dependencySubstitution.apply {
- all { dep ->
- val requested = dep.requested
- if (requested is ModuleComponentSelector) {
- val module = requested.group + ":" + requested.module
- if (
- // todo(b/331800231): remove compiler exception.
- requested.group != "androidx.compose.compiler" &&
- projectModules.containsKey(module)
- ) {
- dep.useTarget(project(projectModules[module]!!))
- }
- }
- }
- }
- }
- }
- }
- }
-
registerStudioTask()
project.tasks.register("listTaskOutputs", ListTaskOutputsTask::class.java) { task ->
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt b/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
index 4def22c..a35febf 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
@@ -29,8 +29,6 @@
interface Parameters : BuildServiceParameters {
var tomlFileName: String
var tomlFileContents: Provider<String>
- var composeCustomVersion: Provider<String>
- var composeCustomGroup: Provider<String>
}
private val parsedTomlFile: TomlParseResult by lazy {
@@ -147,7 +145,7 @@
}
// a LibraryGroupSpec knows how to associate a LibraryGroup with the appropriate projects
-data class LibraryGroupAssociation(
+private data class LibraryGroupAssociation(
// the name of the variable to which it is assigned in the toml file
val declarationName: String,
// the group
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt b/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
index ad554b8..c215eea 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
@@ -142,7 +142,7 @@
val androidLibrariesSetProvider: Provider<Set<String>> = provider {
val androidxAndroidProjects = mutableSetOf<String>()
// Check every project is the project map to see if they are an Android Library
- val projectModules = project.getProjectsMap()
+ val projectModules = extension.mavenCoordinatesToProjectPathMap
for ((mavenCoordinates, projectPath) in projectModules) {
project.findProject(projectPath)?.plugins?.let { plugins ->
if (plugins.hasPlugin(LibraryPlugin::class.java)) {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/MaxDepVersions.kt b/buildSrc/private/src/main/kotlin/androidx/build/MaxDepVersions.kt
new file mode 100644
index 0000000..81a9cfc
--- /dev/null
+++ b/buildSrc/private/src/main/kotlin/androidx/build/MaxDepVersions.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.component.ModuleComponentSelector
+
+/**
+ * If useMaxDepVersions is set, iterate through all the dependencies and substitute any androidx
+ * artifact dependency with the local tip of tree version of the library.
+ */
+internal fun Project.configureMaxDepVersions(extension: AndroidXExtension) {
+ if (!usingMaxDepVersions()) return
+ val projectModules = extension.mavenCoordinatesToProjectPathMap
+ configurations.configureEach { configuration ->
+ configuration.resolutionStrategy.dependencySubstitution.apply {
+ all { dep ->
+ val requested = dep.requested
+ if (requested is ModuleComponentSelector) {
+ val module = requested.group + ":" + requested.module
+ if (
+ // todo(b/331800231): remove compiler exception.
+ requested.group != "androidx.compose.compiler" &&
+ projectModules.containsKey(module)
+ ) {
+ dep.useTarget(project(projectModules[module]!!))
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/Release.kt b/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
index 5a347ab..791d72d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
@@ -18,7 +18,6 @@
import androidx.build.uptodatedness.cacheEvenIfNoOutputs
import java.io.File
import java.io.FileNotFoundException
-import java.util.Locale
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
@@ -121,14 +120,11 @@
object Release {
@Suppress("MemberVisibilityCanBePrivate")
const val PROJECT_ARCHIVE_ZIP_TASK_NAME = "createProjectZip"
- const val DIFF_TASK_PREFIX = "createDiffArchive"
- const val FULL_ARCHIVE_TASK_NAME = "createArchive"
- const val ALL_ARCHIVES_TASK_NAME = "createAllArchives"
+ private const val FULL_ARCHIVE_TASK_NAME = "createArchive"
+ private const val ALL_ARCHIVES_TASK_NAME = "createAllArchives"
const val DEFAULT_PUBLISH_CONFIG = "release"
- const val GROUP_ZIPS_FOLDER = "per-group-zips"
const val PROJECT_ZIPS_FOLDER = "per-project-zips"
- const val GROUP_ZIP_PREFIX = "gmaven"
- const val GLOBAL_ZIP_PREFIX = "top-of-tree-m2repository"
+ private const val GLOBAL_ZIP_PREFIX = "top-of-tree-m2repository"
// lazily created config action params so that we don't keep re-creating them
private var configActionParams: GMavenZipTask.ConfigAction.Params? = null
@@ -158,12 +154,6 @@
)
return
}
-
- val mavenGroup =
- androidXExtension.mavenGroup?.group
- ?: throw IllegalArgumentException(
- "Cannot register a project to release if it does not have a mavenGroup set up"
- )
if (!androidXExtension.isVersionSet()) {
throw IllegalArgumentException(
"Cannot register a project to release if it does not have a mavenVersion set up"
@@ -172,12 +162,7 @@
val version = project.version
val projectZipTask = getProjectZipTask(project)
- val zipTasks =
- listOf(
- projectZipTask,
- getGroupReleaseZipTask(project, mavenGroup),
- getGlobalFullZipTask(project)
- )
+ val zipTasks = listOf(projectZipTask, getGlobalFullZipTask(project))
val artifacts = androidXExtension.publishedArtifacts
val publishTask = project.tasks.named("publish")
@@ -295,28 +280,6 @@
)
}
- /** Creates and returns the zip task that includes artifacts only in the given maven group. */
- private fun getGroupReleaseZipTask(
- project: Project,
- group: String
- ): TaskProvider<GMavenZipTask> {
- return project.rootProject.maybeRegister(
- name = "${DIFF_TASK_PREFIX}For${groupToTaskNameSuffix(group)}",
- onConfigure = { task: GMavenZipTask ->
- GMavenZipTask.ConfigAction(
- getParams(
- project = project,
- distDir = File(project.getDistributionDirectory(), GROUP_ZIPS_FOLDER),
- fileNamePrefix = GROUP_ZIP_PREFIX,
- group = group
- )
- )
- .execute(task)
- },
- onRegister = { taskProvider -> project.addToAnchorTask(taskProvider) }
- )
- }
-
private fun getProjectZipTask(project: Project): TaskProvider<GMavenZipTask> {
val taskProvider =
project.tasks.register(PROJECT_ARCHIVE_ZIP_TASK_NAME, GMavenZipTask::class.java) {
@@ -403,7 +366,7 @@
}
}
- fun verifyFiles() {
+ private fun verifyFiles() {
val missingFiles = mutableListOf<String>()
val emptyDirs = mutableListOf<String>()
filesToVerify.forEach { file ->
@@ -478,15 +441,6 @@
return declaredTargets.toList()
}
-/** Converts the maven group into a readable task name. */
-private fun groupToTaskNameSuffix(group: String): String {
- return group.split('.').joinToString("") {
- it.replaceFirstChar { char ->
- if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else char.toString()
- }
- }
-}
-
private fun Project.projectZipPrefix(): String {
return "${project.group}-${project.name}"
}
@@ -509,17 +463,3 @@
getZipName(projectZipPrefix(), "") +
"-${project.version}.zip"
}
-
-fun Project.getGroupZipPath(): String {
- return Release.GROUP_ZIPS_FOLDER +
- "/" +
- getZipName(Release.GROUP_ZIP_PREFIX, project.group.toString()) +
- ".zip"
-}
-
-fun Project.getGlobalZipFile(): File {
- return File(
- project.getDistributionDirectory(),
- getZipName(Release.GLOBAL_ZIP_PREFIX, "") + ".zip"
- )
-}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
index efe9eee..07f4db2 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
@@ -21,7 +21,6 @@
import androidx.build.LibraryGroup
import androidx.build.docs.CheckTipOfTreeDocsTask.Companion.requiresDocs
import androidx.build.getBuildInfoDirectory
-import androidx.build.getGroupZipPath
import androidx.build.getProjectZipPath
import androidx.build.getSupportRootFolder
import androidx.build.gitclient.getHeadShaProvider
@@ -92,8 +91,6 @@
@get:Input abstract val groupIdRequiresSameVersion: Property<Boolean>
- @get:Input abstract val groupZipPath: Property<String>
-
@get:Input abstract val projectZipPath: Property<String>
@get:[Input Optional]
@@ -139,7 +136,6 @@
libraryBuildInfoFile.path = projectDir.get()
libraryBuildInfoFile.sha = commit.get()
libraryBuildInfoFile.groupIdRequiresSameVersion = groupIdRequiresSameVersion.get()
- libraryBuildInfoFile.groupZipPath = groupZipPath.get()
libraryBuildInfoFile.projectZipPath = projectZipPath.get()
libraryBuildInfoFile.kotlinVersion = kotlinVersion.orNull
libraryBuildInfoFile.checks = ArrayList()
@@ -196,7 +192,6 @@
)
task.commit.set(shaProvider)
task.groupIdRequiresSameVersion.set(mavenGroup?.requireSameVersion ?: false)
- task.groupZipPath.set(project.getGroupZipPath())
task.projectZipPath.set(project.getProjectZipPath())
// Note:
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt b/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt
index 59be856b..8106d92 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt
@@ -25,7 +25,7 @@
/** AndroidX configuration backed by Gradle properties. */
abstract class AndroidConfigImpl(private val project: Project) : AndroidConfig {
- override val buildToolsVersion: String = "35.0.0-rc1"
+ override val buildToolsVersion: String = "35.0.0"
override val compileSdk: Int by lazy {
val sdkString = project.extraPropertyOrNull(COMPILE_SDK)?.toString()
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt
index 6bd2e52..6fe08bc 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt
@@ -18,6 +18,7 @@
import android.hardware.camera2.CameraDevice
import android.media.MediaCodec
+import android.util.Range
import androidx.annotation.VisibleForTesting
import androidx.camera.camera2.pipe.OutputStream
import androidx.camera.camera2.pipe.core.Log
@@ -32,6 +33,7 @@
import androidx.camera.core.UseCase
import androidx.camera.core.impl.DeferrableSurface
import androidx.camera.core.impl.SessionConfig
+import androidx.camera.core.impl.StreamSpec
import androidx.camera.core.impl.UseCaseConfig
import androidx.camera.core.streamsharing.StreamSharing
import kotlinx.coroutines.CoroutineScope
@@ -118,6 +120,15 @@
}
}
+ fun getExpectedFrameRateRange(): Range<Int>? {
+ return if (
+ isSessionConfigValid() &&
+ sessionConfig.expectedFrameRateRange != StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
+ )
+ sessionConfig.expectedFrameRateRange
+ else null
+ }
+
/**
* Populates the mapping between surfaces of a capture session and the Stream Use Case of their
* associated stream.
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index b832f7b..0b760d2 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -874,6 +874,8 @@
}
}
+ // Set fps range to capture request
+ val targetFpsRange = sessionConfigAdapter.getExpectedFrameRateRange()
val defaultParameters =
buildMap<Any, Any?> {
if (isExtensions) {
@@ -884,7 +886,13 @@
CameraPipeKeys.camera2CaptureRequestTag,
"android.hardware.camera2.CaptureRequest.setTag.CX"
)
+ targetFpsRange?.let {
+ set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetFpsRange)
+ }
}
+ targetFpsRange?.let {
+ sessionParameters[CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE] = targetFpsRange
+ }
// TODO: b/327517884 - Add a quirk to not abort captures on stop for certain OEMs during
// extension sessions.
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
index a11268a..7bce113 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
@@ -18,7 +18,9 @@
import android.graphics.SurfaceTexture
import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
import android.os.Build
+import android.util.Range
import android.view.Surface
import androidx.camera.core.impl.DeferrableSurface
import androidx.camera.core.impl.SessionConfig
@@ -121,6 +123,34 @@
}
@Test
+ fun getExpectedFrameRateRange() {
+ // Arrange
+ val testDeferrableSurface = createTestDeferrableSurface()
+
+ // Create an invalid SessionConfig which doesn't set the template
+ val fakeTestUseCase = createFakeTestUseCase {
+ it.setupSessionConfig(
+ SessionConfig.Builder().also { sessionConfigBuilder ->
+ sessionConfigBuilder.addSurface(testDeferrableSurface)
+ sessionConfigBuilder.setTemplateType(TEMPLATE_PREVIEW)
+ sessionConfigBuilder.setExpectedFrameRateRange(Range(15, 24))
+ }
+ )
+ }
+
+ // Act
+ val sessionConfigAdapter = SessionConfigAdapter(useCases = listOf(fakeTestUseCase))
+
+ // Assert
+ assertThat(sessionConfigAdapter.isSessionConfigValid()).isTrue()
+ assertThat(sessionConfigAdapter.getValidSessionConfigOrNull()).isNotNull()
+ assertThat(sessionConfigAdapter.getExpectedFrameRateRange()).isEqualTo(Range(15, 24))
+
+ // Clean up
+ testDeferrableSurface.close()
+ }
+
+ @Test
fun populateSurfaceToStreamUseCaseMappingEmptyUseCase() {
val mapping = sessionConfigAdapter.getSurfaceToStreamUseCaseMapping(listOf(), listOf())
TestCase.assertTrue(mapping.isEmpty())
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
index 89ed085..0e24668 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
@@ -19,11 +19,14 @@
import android.content.Context
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
import android.hardware.camera2.CameraDevice.TEMPLATE_RECORD
import android.hardware.camera2.CameraMetadata.CONTROL_CAPTURE_INTENT_PREVIEW
+import android.hardware.camera2.CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE
import android.hardware.camera2.CaptureRequest.CONTROL_CAPTURE_INTENT
import android.hardware.camera2.params.SessionConfiguration.SESSION_HIGH_SPEED
import android.os.Build
+import android.util.Range
import android.util.Size
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraGraph.OperatingMode.Companion.HIGH_SPEED
@@ -523,6 +526,40 @@
}
@Test
+ fun createCameraGraphConfig_setTargetFpsRange() = runTest {
+ // Arrange
+ initializeUseCaseThreads(this)
+ val useCaseManager = createUseCaseManager()
+ val fakeUseCase =
+ FakeUseCase().apply {
+ updateSessionConfigForTesting(
+ SessionConfig.Builder()
+ .setTemplateType(TEMPLATE_PREVIEW)
+ .setExpectedFrameRateRange(Range(15, 24))
+ .build()
+ )
+ }
+ val sessionConfigAdapter = SessionConfigAdapter(setOf(fakeUseCase))
+ val streamConfigMap = mutableMapOf<CameraStream.Config, DeferrableSurface>()
+
+ // Act
+ val graphConfig =
+ useCaseManager.createCameraGraphConfig(
+ sessionConfigAdapter,
+ streamConfigMap,
+ )
+
+ // Assert
+ assertThat(graphConfig.sessionTemplate).isEqualTo(RequestTemplate(TEMPLATE_PREVIEW))
+ assertThat(graphConfig.sessionParameters).containsKey(CONTROL_AE_TARGET_FPS_RANGE)
+ assertThat(graphConfig.sessionParameters[CONTROL_AE_TARGET_FPS_RANGE])
+ .isEqualTo(Range(15, 24))
+ assertThat(graphConfig.defaultParameters).containsKey(CONTROL_AE_TARGET_FPS_RANGE)
+ assertThat(graphConfig.defaultParameters[CONTROL_AE_TARGET_FPS_RANGE])
+ .isEqualTo(Range(15, 24))
+ }
+
+ @Test
fun overrideTemplateParams() = runTest {
// Arrange
initializeUseCaseThreads(this)
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/StreamUseCaseTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/StreamUseCaseTest.kt
index 8055baf..7ecd100 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/StreamUseCaseTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/StreamUseCaseTest.kt
@@ -26,10 +26,10 @@
import androidx.camera.camera2.pipe.integration.adapter.SupportedSurfaceCombination
import androidx.camera.camera2.pipe.integration.impl.Camera2ImplConfig
import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
+import androidx.camera.core.CompositionSettings
import androidx.camera.core.DynamicRange
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCapture.CaptureMode
-import androidx.camera.core.LayoutSettings
import androidx.camera.core.UseCase
import androidx.camera.core.impl.AttachedSurfaceInfo
import androidx.camera.core.impl.CameraMode
@@ -644,8 +644,8 @@
StreamSharing(
FakeCamera(),
null,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
children,
useCaseConfigFactory
)
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
index 76d5559..e94c4cb 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
@@ -71,8 +71,8 @@
import androidx.camera.core.Camera;
import androidx.camera.core.CameraControl;
import androidx.camera.core.CameraSelector;
+import androidx.camera.core.CompositionSettings;
import androidx.camera.core.ImageCapture;
-import androidx.camera.core.LayoutSettings;
import androidx.camera.core.Preview;
import androidx.camera.core.UseCase;
import androidx.camera.core.impl.CameraCaptureCallback;
@@ -985,8 +985,8 @@
StreamSharing streamSharing =
new StreamSharing(mCamera2CameraImpl, null,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
children, useCaseConfigFactory);
FakeUseCaseConfig.Builder configBuilder =
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
index 70decc2..08ccc13 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
@@ -237,6 +237,8 @@
applyTemplateParamsOverrideWorkaround(builder, captureConfig.getTemplateType(),
templateParamsOverride);
+ applyAeFpsRange(captureConfig, builder);
+
applyImplementationOptionToCaptureBuilder(builder,
captureConfig.getImplementationOptions());
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/FocusMeteringControl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/FocusMeteringControl.java
index 6129f59..03633e9 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/FocusMeteringControl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/FocusMeteringControl.java
@@ -458,6 +458,8 @@
*/
@ExecutedBy("mExecutor")
void triggerAePrecapture(@Nullable Completer<Void> completer) {
+ Logger.d(TAG, "triggerAePrecapture");
+
if (!mIsActive) {
if (completer != null) {
completer.setException(
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
index 301336d..9984444 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
@@ -40,9 +40,9 @@
import androidx.camera.camera2.impl.Camera2ImplConfig;
import androidx.camera.camera2.internal.SupportedSurfaceCombination.FeatureSettings;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.core.CompositionSettings;
import androidx.camera.core.DynamicRange;
import androidx.camera.core.ImageCapture;
-import androidx.camera.core.LayoutSettings;
import androidx.camera.core.UseCase;
import androidx.camera.core.impl.AttachedSurfaceInfo;
import androidx.camera.core.impl.CameraMode;
@@ -483,7 +483,7 @@
children.add(new FakeUseCase(new FakeUseCaseConfig.Builder().getUseCaseConfig(),
UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE));
StreamSharing streamSharing = new StreamSharing(new FakeCamera(), null,
- LayoutSettings.DEFAULT, LayoutSettings.DEFAULT, children,
+ CompositionSettings.DEFAULT, CompositionSettings.DEFAULT, children,
useCaseConfigFactory);
Map<Integer, AttachedSurfaceInfo> surfaceConfigAttachedSurfaceInfoMap =
new HashMap<>();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CompositionSettings.java b/camera/camera-core/src/main/java/androidx/camera/core/CompositionSettings.java
new file mode 100644
index 0000000..0151734
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CompositionSettings.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core;
+
+import androidx.annotation.FloatRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.core.util.Pair;
+
+/**
+ * Composition settings for dual concurrent camera. It includes alpha value for blending,
+ * offset in x, y coordinates, scale of width and height. The offset, width and height are specified
+ * in normalized device coordinates.
+ *
+ * @see <a href="https://learnopengl.com/Getting-started/Coordinate-Systems">Normalized Device Coordinates</a>
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class CompositionSettings {
+
+ public static final CompositionSettings DEFAULT = new Builder()
+ .setAlpha(1.0f)
+ .setOffset(0.0f, 0.0f)
+ .setScale(1.0f, 1.0f)
+ .build();
+
+ private final float mAlpha;
+ private final Pair<Float, Float> mOffset;
+ private final Pair<Float, Float> mScale;
+
+ private CompositionSettings(
+ float alpha,
+ Pair<Float, Float> offset,
+ Pair<Float, Float> scale) {
+ mAlpha = alpha;
+ mOffset = offset;
+ mScale = scale;
+ }
+
+ /**
+ * Gets the alpha.
+ *
+ * @return alpha value.
+ */
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ /**
+ * Gets the offset.
+ *
+ * @return offset value.
+ */
+ @NonNull
+ public Pair<Float, Float> getOffset() {
+ return mOffset;
+ }
+
+ /**
+ * Gets the scale. Negative value means mirroring in X or Y direction.
+ *
+ * @return scale value.
+ */
+ @NonNull
+ public Pair<Float, Float> getScale() {
+ return mScale;
+ }
+
+ /** A builder for {@link CompositionSettings} instances. */
+ public static final class Builder {
+ private float mAlpha;
+ private Pair<Float, Float> mOffset;
+ private Pair<Float, Float> mScale;
+
+ /** Creates a new {@link Builder}. */
+ public Builder() {
+ mAlpha = 1.0f;
+ mOffset = Pair.create(0.0f, 0.0f);
+ mScale = Pair.create(1.0f, 1.0f);
+ }
+
+ /**
+ * Sets the alpha.
+ *
+ * @param alpha alpha value.
+ * @return Builder instance.
+ */
+ @NonNull
+ public Builder setAlpha(@FloatRange(from = 0, to = 1) float alpha) {
+ mAlpha = alpha;
+ return this;
+ }
+
+ /**
+ * Sets the offset.
+ *
+ * @param offsetX offset X value.
+ * @param offsetY offset Y value.
+ * @return Builder instance.
+ */
+ @NonNull
+ public Builder setOffset(
+ @FloatRange(from = -1, to = 1) float offsetX,
+ @FloatRange(from = -1, to = 1) float offsetY) {
+ mOffset = Pair.create(offsetX, offsetY);
+ return this;
+ }
+
+ /**
+ * Sets the scale.
+ *
+ * @param scaleX scale X value.
+ * @param scaleY scale Y value.
+ * @return Builder instance.
+ */
+ @NonNull
+ public Builder setScale(
+ @FloatRange(from = -1, to = 1) float scaleX,
+ @FloatRange(from = -1, to = 1) float scaleY) {
+ mScale = Pair.create(scaleX, scaleY);
+ return this;
+ }
+
+ /**
+ * Builds the {@link CompositionSettings}.
+ *
+ * @return {@link CompositionSettings}.
+ */
+ @NonNull
+ public CompositionSettings build() {
+ return new CompositionSettings(
+ mAlpha,
+ mOffset,
+ mScale);
+ }
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ConcurrentCamera.java b/camera/camera-core/src/main/java/androidx/camera/core/ConcurrentCamera.java
index 6507aff..e8d2997 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ConcurrentCamera.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ConcurrentCamera.java
@@ -79,7 +79,7 @@
@NonNull
private UseCaseGroup mUseCaseGroup;
@NonNull
- private LayoutSettings mLayoutSettings;
+ private CompositionSettings mCompositionSettings;
/**
* Constructor of a {@link SingleCameraConfig} for concurrent cameras.
@@ -92,7 +92,7 @@
@NonNull CameraSelector cameraSelector,
@NonNull UseCaseGroup useCaseGroup,
@NonNull LifecycleOwner lifecycleOwner) {
- this(cameraSelector, useCaseGroup, LayoutSettings.DEFAULT, lifecycleOwner);
+ this(cameraSelector, useCaseGroup, CompositionSettings.DEFAULT, lifecycleOwner);
}
/**
@@ -100,18 +100,18 @@
*
* @param cameraSelector {@link CameraSelector}.
* @param useCaseGroup {@link UseCaseGroup}.
- * @param layoutSettings {@link LayoutSettings}.
+ * @param compositionSettings {@link CompositionSettings}.
* @param lifecycleOwner {@link LifecycleOwner}.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public SingleCameraConfig(
@NonNull CameraSelector cameraSelector,
@NonNull UseCaseGroup useCaseGroup,
- @NonNull LayoutSettings layoutSettings,
+ @NonNull CompositionSettings compositionSettings,
@NonNull LifecycleOwner lifecycleOwner) {
this.mCameraSelector = cameraSelector;
this.mUseCaseGroup = useCaseGroup;
- this.mLayoutSettings = layoutSettings;
+ this.mCompositionSettings = compositionSettings;
this.mLifecycleOwner = lifecycleOwner;
}
@@ -143,13 +143,13 @@
}
/**
- * Returns {@link LayoutSettings}.
- * @return {@link LayoutSettings} instance.
+ * Returns {@link CompositionSettings}.
+ * @return {@link CompositionSettings} instance.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@NonNull
- public LayoutSettings getLayoutSettings() {
- return mLayoutSettings;
+ public CompositionSettings getCompositionSettings() {
+ return mCompositionSettings;
}
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/LayoutSettings.java b/camera/camera-core/src/main/java/androidx/camera/core/LayoutSettings.java
deleted file mode 100644
index 68e098c..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/LayoutSettings.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.core;
-
-import androidx.annotation.FloatRange;
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-/**
- * Layout settings for dual concurrent camera. It includes alpha value for blending,
- * offset in x, y coordinates, width and height. The offset, width and height are specified
- * in normalized device coordinates.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class LayoutSettings {
-
- public static final LayoutSettings DEFAULT = new Builder()
- .setAlpha(1.0f)
- .setOffsetX(0.0f)
- .setOffsetY(0.0f)
- .setWidth(1.0f)
- .setHeight(1.0f)
- .build();
-
- private final float mAlpha;
- private final float mOffsetX;
- private final float mOffsetY;
- private final float mWidth;
- private final float mHeight;
-
- private LayoutSettings(
- float alpha,
- float offsetX,
- float offsetY,
- float width,
- float height) {
- this.mAlpha = alpha;
- this.mOffsetX = offsetX;
- this.mOffsetY = offsetY;
- this.mWidth = width;
- this.mHeight = height;
- }
-
- /**
- * Gets the alpha.
- *
- * @return alpha value.
- */
- public float getAlpha() {
- return mAlpha;
- }
-
- /**
- * Gets the offset X.
- *
- * @return offset X value.
- */
- public float getOffsetX() {
- return mOffsetX;
- }
-
- /**
- * Gets the offset Y.
- *
- * @return offset Y value.
- */
- public float getOffsetY() {
- return mOffsetY;
- }
-
- /**
- * Gets the width.
- *
- * @return width.
- */
- public float getWidth() {
- return mWidth;
- }
-
- /**
- * Gets the height.
- *
- * @return height.
- */
- public float getHeight() {
- return mHeight;
- }
-
- /** A builder for {@link LayoutSettings} instances. */
- public static final class Builder {
- private float mAlpha;
- private float mOffsetX;
- private float mOffsetY;
- private float mWidth;
- private float mHeight;
-
- /** Creates a new {@link Builder}. */
- public Builder() {
- mAlpha = 1.0f;
- mOffsetX = 0.0f;
- mOffsetY = 0.0f;
- mWidth = 0.0f;
- mHeight = 0.0f;
- }
-
- /**
- * Sets the alpha.
- *
- * @param alpha alpha value.
- * @return Builder instance.
- */
- @NonNull
- public Builder setAlpha(@FloatRange(from = 0, to = 1) float alpha) {
- this.mAlpha = alpha;
- return this;
- }
-
- /**
- * Sets the offset X.
- *
- * @param offsetX offset X value.
- * @return Builder instance.
- */
- @NonNull
- public Builder setOffsetX(@FloatRange(from = -1, to = 1) float offsetX) {
- this.mOffsetX = offsetX;
- return this;
- }
-
- /**
- * Sets the offset Y.
- *
- * @param offsetY offset Y value.
- * @return Builder instance.
- */
- @NonNull
- public Builder setOffsetY(@FloatRange(from = -1, to = 1) float offsetY) {
- this.mOffsetY = offsetY;
- return this;
- }
-
- /**
- * Sets the width.
- *
- * @param width width value.
- * @return Builder instance.
- */
- @NonNull
- public Builder setWidth(@FloatRange(from = -1, to = 1) float width) {
- this.mWidth = width;
- return this;
- }
-
- /**
- * Sets the height.
- *
- * @param height height value.
- * @return Builder instance.
- */
- @NonNull
- public Builder setHeight(@FloatRange(from = -1, to = 1) float height) {
- this.mHeight = height;
- return this;
- }
-
- /**
- * Builds the {@link LayoutSettings}.
- *
- * @return {@link LayoutSettings}.
- */
- @NonNull
- public LayoutSettings build() {
- return new LayoutSettings(
- mAlpha,
- mOffsetX,
- mOffsetY,
- mWidth,
- mHeight);
- }
- }
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index b233749..1725729 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -55,9 +55,9 @@
import androidx.camera.core.CameraEffect;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraSelector;
+import androidx.camera.core.CompositionSettings;
import androidx.camera.core.DynamicRange;
import androidx.camera.core.ImageCapture;
-import androidx.camera.core.LayoutSettings;
import androidx.camera.core.Logger;
import androidx.camera.core.Preview;
import androidx.camera.core.UseCase;
@@ -173,9 +173,9 @@
private final RestrictedCameraInfo mAdapterSecondaryCameraInfo;
@NonNull
- private final LayoutSettings mLayoutSettings;
+ private final CompositionSettings mCompositionSettings;
@NonNull
- private final LayoutSettings mSecondaryLayoutSettings;
+ private final CompositionSettings mSecondaryCompositionSettings;
/**
* Create a new {@link CameraUseCaseAdapter} instance.
@@ -196,8 +196,8 @@
new RestrictedCameraInfo(camera.getCameraInfoInternal(),
CameraConfigs.defaultConfig()),
null,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
cameraCoordinator,
cameraDeviceSurfaceManager,
useCaseConfigFactory);
@@ -212,10 +212,10 @@
* information to configure the {@link CameraInternal} when
* attaching the uses cases of this adapter to the camera.
* @param secondaryRestrictedCameraInfo The {@link RestrictedCameraInfo} of secondary camera.
- * @param layoutSettings The layout settings that will be used to configure the
+ * @param compositionSettings The composition settings that will be used to configure the
* camera.
- * @param secondaryLayoutSettings The layout settings that will be used to configure the
- * secondary camera.
+ * @param secondaryCompositionSettings The composition settings that will be used to configure
+ * the secondary camera.
* @param cameraCoordinator Camera coordinator that exposes concurrent camera mode.
* @param cameraDeviceSurfaceManager A class that checks for whether a specific camera
* can support the set of Surface with set resolutions.
@@ -227,15 +227,15 @@
@Nullable CameraInternal secondaryCamera,
@NonNull RestrictedCameraInfo restrictedCameraInfo,
@Nullable RestrictedCameraInfo secondaryRestrictedCameraInfo,
- @NonNull LayoutSettings layoutSettings,
- @NonNull LayoutSettings secondaryLayoutSettings,
+ @NonNull CompositionSettings compositionSettings,
+ @NonNull CompositionSettings secondaryCompositionSettings,
@NonNull CameraCoordinator cameraCoordinator,
@NonNull CameraDeviceSurfaceManager cameraDeviceSurfaceManager,
@NonNull UseCaseConfigFactory useCaseConfigFactory) {
mCameraInternal = camera;
mSecondaryCameraInternal = secondaryCamera;
- mLayoutSettings = layoutSettings;
- mSecondaryLayoutSettings = secondaryLayoutSettings;
+ mCompositionSettings = compositionSettings;
+ mSecondaryCompositionSettings = secondaryCompositionSettings;
mCameraCoordinator = cameraCoordinator;
mCameraDeviceSurfaceManager = cameraDeviceSurfaceManager;
mUseCaseConfigFactory = useCaseConfigFactory;
@@ -622,8 +622,8 @@
return new StreamSharing(mCameraInternal,
mSecondaryCameraInternal,
- mLayoutSettings,
- mSecondaryLayoutSettings,
+ mCompositionSettings,
+ mSecondaryCompositionSettings,
newChildren,
mUseCaseConfigFactory);
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualOpenGlRenderer.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualOpenGlRenderer.java
index fc7fe62..cb5fdfc 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualOpenGlRenderer.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualOpenGlRenderer.java
@@ -34,8 +34,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
+import androidx.camera.core.CompositionSettings;
import androidx.camera.core.DynamicRange;
-import androidx.camera.core.LayoutSettings;
import androidx.camera.core.Logger;
import androidx.camera.core.SurfaceOutput;
import androidx.camera.core.processing.OpenGlRenderer;
@@ -61,15 +61,15 @@
private int mSecondaryExternalTextureId = -1;
@NonNull
- private final LayoutSettings mPrimaryLayoutSettings;
+ private final CompositionSettings mPrimaryCompositionSettings;
@NonNull
- private final LayoutSettings mSecondaryLayoutSettings;
+ private final CompositionSettings mSecondaryCompositionSettings;
public DualOpenGlRenderer(
- @NonNull LayoutSettings primaryLayoutSettings,
- @NonNull LayoutSettings secondaryLayoutSettings) {
- mPrimaryLayoutSettings = primaryLayoutSettings;
- mSecondaryLayoutSettings = secondaryLayoutSettings;
+ @NonNull CompositionSettings primaryCompositionSettings,
+ @NonNull CompositionSettings secondaryCompositionSettings) {
+ mPrimaryCompositionSettings = primaryCompositionSettings;
+ mSecondaryCompositionSettings = secondaryCompositionSettings;
}
@NonNull
@@ -138,11 +138,11 @@
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
// Primary Camera
renderInternal(outputSurface, surfaceOutput, primarySurfaceTexture,
- mPrimaryLayoutSettings, mPrimaryExternalTextureId, true);
+ mPrimaryCompositionSettings, mPrimaryExternalTextureId, true);
// Secondary Camera
// Only use primary camera info for output surface
renderInternal(outputSurface, surfaceOutput, secondarySurfaceTexture,
- mSecondaryLayoutSettings, mSecondaryExternalTextureId, true);
+ mSecondaryCompositionSettings, mSecondaryExternalTextureId, true);
EGLExt.eglPresentationTimeANDROID(mEglDisplay, outputSurface.getEglSurface(), timestampNs);
@@ -157,7 +157,7 @@
@NonNull OutputSurface outputSurface,
@NonNull SurfaceOutput surfaceOutput,
@NonNull SurfaceTexture surfaceTexture,
- @NonNull LayoutSettings layoutSettings,
+ @NonNull CompositionSettings compositionSettings,
int externalTextureId,
boolean isPrimary) {
useAndConfigureProgramWithTexture(externalTextureId);
@@ -179,13 +179,13 @@
}
float[] transTransform = getTransformMatrix(
- new Size((int) (outputSurface.getWidth() * layoutSettings.getWidth()),
- (int) (outputSurface.getHeight() * layoutSettings.getHeight())),
+ new Size((int) (outputSurface.getWidth() * compositionSettings.getScale().first),
+ (int) (outputSurface.getHeight() * compositionSettings.getScale().second)),
new Size(outputSurface.getWidth(), outputSurface.getHeight()),
- layoutSettings);
+ compositionSettings);
currentProgram.updateTransformMatrix(transTransform);
- currentProgram.updateAlpha(layoutSettings.getAlpha());
+ currentProgram.updateAlpha(compositionSettings.getAlpha());
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFuncSeparate(
@@ -204,7 +204,7 @@
private static float[] getTransformMatrix(
@NonNull Size overlaySize,
@NonNull Size backgroundSize,
- @NonNull LayoutSettings layoutSettings) {
+ @NonNull CompositionSettings compositionSettings) {
float[] aspectRatioMatrix = create4x4IdentityMatrix();
float[] overlayFrameAnchorMatrix = create4x4IdentityMatrix();
float[] transformationMatrix = create4x4IdentityMatrix();
@@ -217,12 +217,15 @@
/* z= */ 1.0f);
// Translate the image.
- Matrix.translateM(
- overlayFrameAnchorMatrix,
- /* mOffset= */ 0,
- layoutSettings.getOffsetX() / layoutSettings.getWidth(),
- layoutSettings.getOffsetY() / layoutSettings.getHeight(),
- /* z= */ 0.0f);
+ if (compositionSettings.getScale().first != 0.0f
+ || compositionSettings.getScale().second != 0.0f) {
+ Matrix.translateM(
+ overlayFrameAnchorMatrix,
+ /* mOffset= */ 0,
+ compositionSettings.getOffset().first / compositionSettings.getScale().first,
+ compositionSettings.getOffset().second / compositionSettings.getScale().second,
+ /* z= */ 0.0f);
+ }
// Correct for aspect ratio of image in output frame.
Matrix.multiplyMM(
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualSurfaceProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualSurfaceProcessor.java
index afa52f8..3f5be70 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualSurfaceProcessor.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualSurfaceProcessor.java
@@ -27,8 +27,8 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
+import androidx.camera.core.CompositionSettings;
import androidx.camera.core.DynamicRange;
-import androidx.camera.core.LayoutSettings;
import androidx.camera.core.Logger;
import androidx.camera.core.ProcessingException;
import androidx.camera.core.SurfaceOutput;
@@ -81,21 +81,23 @@
private SurfaceTexture mSecondarySurfaceTexture;
DualSurfaceProcessor(@NonNull DynamicRange dynamicRange,
- @NonNull LayoutSettings primaryLayoutSettings,
- @NonNull LayoutSettings secondaryLayoutSettings) {
- this(dynamicRange, Collections.emptyMap(), primaryLayoutSettings, secondaryLayoutSettings);
+ @NonNull CompositionSettings primaryCompositionSettings,
+ @NonNull CompositionSettings secondaryCompositionSettings) {
+ this(dynamicRange, Collections.emptyMap(),
+ primaryCompositionSettings, secondaryCompositionSettings);
}
DualSurfaceProcessor(
@NonNull DynamicRange dynamicRange,
@NonNull Map<InputFormat, ShaderProvider> shaderProviderOverrides,
- @NonNull LayoutSettings primaryLayoutSettings,
- @NonNull LayoutSettings secondaryLayoutSettings) {
+ @NonNull CompositionSettings primaryCompositionSettings,
+ @NonNull CompositionSettings secondaryCompositionSettings) {
mGlThread = new HandlerThread("GL Thread");
mGlThread.start();
mGlHandler = new Handler(mGlThread.getLooper());
mGlExecutor = CameraXExecutors.newHandlerExecutor(mGlHandler);
- mGlRenderer = new DualOpenGlRenderer(primaryLayoutSettings, secondaryLayoutSettings);
+ mGlRenderer = new DualOpenGlRenderer(
+ primaryCompositionSettings, secondaryCompositionSettings);
try {
initGlRenderer(dynamicRange, shaderProviderOverrides);
} catch (RuntimeException e) {
@@ -263,8 +265,9 @@
private Factory() {
}
- private static Function3<DynamicRange, LayoutSettings,
- LayoutSettings, SurfaceProcessorInternal> sSupplier = DualSurfaceProcessor::new;
+ private static Function3<DynamicRange, CompositionSettings,
+ CompositionSettings, SurfaceProcessorInternal> sSupplier =
+ DualSurfaceProcessor::new;
/**
* Creates a new {@link DefaultSurfaceProcessor} with no-op shader.
@@ -272,9 +275,10 @@
@NonNull
public static SurfaceProcessorInternal newInstance(
@NonNull DynamicRange dynamicRange,
- @NonNull LayoutSettings primaryLayoutSettings,
- @NonNull LayoutSettings secondaryLayoutSettings) {
- return sSupplier.invoke(dynamicRange, primaryLayoutSettings, secondaryLayoutSettings);
+ @NonNull CompositionSettings primaryCompositionSettings,
+ @NonNull CompositionSettings secondaryCompositionSettings) {
+ return sSupplier.invoke(dynamicRange,
+ primaryCompositionSettings, secondaryCompositionSettings);
}
/**
@@ -283,8 +287,8 @@
@VisibleForTesting
public static void setSupplier(
@NonNull Function3<DynamicRange,
- LayoutSettings,
- LayoutSettings,
+ CompositionSettings,
+ CompositionSettings,
SurfaceProcessorInternal> supplier) {
sSupplier = supplier;
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
index 8afe48f..99a6d8d 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
@@ -44,8 +44,8 @@
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.camera.core.CameraEffect;
+import androidx.camera.core.CompositionSettings;
import androidx.camera.core.ImageCapture;
-import androidx.camera.core.LayoutSettings;
import androidx.camera.core.MirrorMode;
import androidx.camera.core.UseCase;
import androidx.camera.core.impl.CameraInfoInternal;
@@ -89,12 +89,12 @@
@NonNull
private final VirtualCameraAdapter mVirtualCameraAdapter;
- // The layout settings of primary camera in dual camera case.
+ // The composition settings of primary camera in dual camera case.
@NonNull
- private final LayoutSettings mLayoutSettings;
- // The layout settings of secondary camera in dual camera case.
+ private final CompositionSettings mCompositionSettings;
+ // The composition settings of secondary camera in dual camera case.
@NonNull
- private final LayoutSettings mSecondaryLayoutSettings;
+ private final CompositionSettings mSecondaryCompositionSettings;
// Node that applies effect to the input.
@Nullable
private SurfaceProcessorNode mEffectNode;
@@ -149,14 +149,14 @@
*/
public StreamSharing(@NonNull CameraInternal camera,
@Nullable CameraInternal secondaryCamera,
- @NonNull LayoutSettings layoutSettings,
- @NonNull LayoutSettings secondaryLayoutSettings,
+ @NonNull CompositionSettings compositionSettings,
+ @NonNull CompositionSettings secondaryCompositionSettings,
@NonNull Set<UseCase> children,
@NonNull UseCaseConfigFactory useCaseConfigFactory) {
super(getDefaultConfig(children));
mDefaultConfig = getDefaultConfig(children);
- mLayoutSettings = layoutSettings;
- mSecondaryLayoutSettings = secondaryLayoutSettings;
+ mCompositionSettings = compositionSettings;
+ mSecondaryCompositionSettings = secondaryCompositionSettings;
mVirtualCameraAdapter = new VirtualCameraAdapter(
camera, secondaryCamera, children, useCaseConfigFactory,
(jpegQuality, rotationDegrees) -> {
@@ -316,8 +316,8 @@
getCamera(),
getSecondaryCamera(),
primaryStreamSpec, // use primary stream spec
- mLayoutSettings,
- mSecondaryLayoutSettings);
+ mCompositionSettings,
+ mSecondaryCompositionSettings);
boolean isViewportSet = getViewPortCropRect() != null;
Map<UseCase, DualOutConfig> outConfigMap =
mVirtualCameraAdapter.getChildrenOutConfigs(
@@ -511,14 +511,14 @@
@NonNull CameraInternal primaryCamera,
@NonNull CameraInternal secondaryCamera,
@NonNull StreamSpec streamSpec,
- @NonNull LayoutSettings primaryLayoutSettings,
- @NonNull LayoutSettings secondaryLayoutSettings) {
+ @NonNull CompositionSettings primaryCompositionSettings,
+ @NonNull CompositionSettings secondaryCompositionSettings) {
// TODO: handle EffectNode for dual camera case
return new DualSurfaceProcessorNode(primaryCamera, secondaryCamera,
DualSurfaceProcessor.Factory.newInstance(
streamSpec.getDynamicRange(),
- primaryLayoutSettings,
- secondaryLayoutSettings));
+ primaryCompositionSettings,
+ secondaryCompositionSettings));
}
private int getRotationAppliedByEffect() {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
index a28d501..42652ae 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
@@ -30,6 +30,7 @@
import androidx.camera.core.CameraEffect
import androidx.camera.core.CameraEffect.PREVIEW
import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
+import androidx.camera.core.CompositionSettings
import androidx.camera.core.DynamicRange.HDR_UNSPECIFIED_10_BIT
import androidx.camera.core.FocusMeteringAction
import androidx.camera.core.FocusMeteringAction.FLAG_AE
@@ -37,7 +38,6 @@
import androidx.camera.core.FocusMeteringAction.FLAG_AWB
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
-import androidx.camera.core.LayoutSettings
import androidx.camera.core.Preview
import androidx.camera.core.SurfaceOrientedMeteringPointFactory
import androidx.camera.core.TorchState
@@ -317,8 +317,8 @@
null,
RestrictedCameraInfo(fakeCamera.cameraInfoInternal, extensionsConfig),
null,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
FakeCameraCoordinator(),
fakeManager,
FakeUseCaseConfigFactory(),
@@ -364,8 +364,8 @@
StreamSharing(
fakeCamera,
null,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(preview, video),
useCaseConfigFactory
)
@@ -1388,8 +1388,8 @@
RestrictedCameraInfo(camera.cameraInfoInternal, cameraConfig),
if (secondaryCamera == null) null
else RestrictedCameraInfo(secondaryCamera.cameraInfoInternal, cameraConfig),
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
cameraCoordinator,
fakeCameraDeviceSurfaceManager,
useCaseConfigFactory
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt
index c5e0c42..72f99ff 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt
@@ -37,13 +37,13 @@
import androidx.camera.core.CameraEffect.PREVIEW
import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
import androidx.camera.core.CameraSelector.LENS_FACING_FRONT
+import androidx.camera.core.CompositionSettings
import androidx.camera.core.DynamicRange
import androidx.camera.core.DynamicRange.HLG_10_BIT
import androidx.camera.core.DynamicRange.SDR
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY
import androidx.camera.core.ImageProxy
-import androidx.camera.core.LayoutSettings
import androidx.camera.core.Preview
import androidx.camera.core.SurfaceRequest
import androidx.camera.core.impl.CameraCaptureCallback
@@ -134,8 +134,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(child1, child2),
useCaseConfigFactory
)
@@ -171,8 +171,8 @@
StreamSharing(
frontCamera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(preview, videoCapture),
useCaseConfigFactory
)
@@ -207,8 +207,8 @@
StreamSharing(
frontCamera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(child1),
useCaseConfigFactory
)
@@ -230,8 +230,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(child1),
useCaseConfigFactory
)
@@ -260,8 +260,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(child1),
useCaseConfigFactory
)
@@ -358,8 +358,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(child1, imageCapture),
useCaseConfigFactory
)
@@ -415,8 +415,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(unspecifiedChild, hdrChild),
useCaseConfigFactory
)
@@ -452,8 +452,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(sdrChild, hdrChild),
useCaseConfigFactory
)
@@ -535,8 +535,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(child1, imageCapture),
useCaseConfigFactory
)
@@ -569,8 +569,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(child1, imageCapture),
useCaseConfigFactory
)
@@ -605,8 +605,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(child1),
useCaseConfigFactory
)
@@ -628,8 +628,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(child1),
useCaseConfigFactory
)
@@ -655,8 +655,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(previewBuilder.build()),
Camera2UseCaseConfigFactory(context)
)
@@ -873,8 +873,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(preview, videoCapture),
useCaseConfigFactory
)
@@ -902,8 +902,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(preview, videoCapture),
useCaseConfigFactory
)
@@ -928,8 +928,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(preview, imageCapture),
useCaseConfigFactory
)
@@ -952,8 +952,8 @@
StreamSharing(
camera,
secondaryCamera,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
setOf(preview, imageCapture, videoCapture),
useCaseConfigFactory
)
diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
index 35bbda9..b137a04 100644
--- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
+++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
@@ -22,7 +22,7 @@
import static java.util.Collections.emptyList;
-import androidx.camera.core.LayoutSettings;
+import androidx.camera.core.CompositionSettings;
import androidx.camera.core.concurrent.CameraCoordinator;
import androidx.camera.core.impl.CameraConfig;
import androidx.camera.core.impl.CameraConfigs;
@@ -633,8 +633,8 @@
new RestrictedCameraInfo((CameraInfoInternal) mCamera.getCameraInfo(),
cameraConfig),
null,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
mCameraCoordinator,
new FakeCameraDeviceSurfaceManager(),
new FakeUseCaseConfigFactory());
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt
index 8e1c9b4..1ac2586 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt
@@ -32,13 +32,13 @@
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraX
import androidx.camera.core.CameraXConfig
+import androidx.camera.core.CompositionSettings
import androidx.camera.core.ConcurrentCamera
import androidx.camera.core.ConcurrentCamera.SingleCameraConfig
import androidx.camera.core.ExperimentalCameraInfo
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.InitializationException
-import androidx.camera.core.LayoutSettings
import androidx.camera.core.Preview
import androidx.camera.core.UseCase
import androidx.camera.core.UseCaseGroup
@@ -214,8 +214,8 @@
lifecycleOwner,
cameraSelector,
null,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
null,
emptyList<CameraEffect>(),
*useCases
@@ -254,8 +254,8 @@
lifecycleOwner,
cameraSelector,
null,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
useCaseGroup.viewPort,
useCaseGroup.effects,
*useCaseGroup.useCases.toTypedArray<UseCase>()
@@ -285,7 +285,7 @@
* If the concurrent logical cameras are binding the same preview and video capture use cases,
* the concurrent cameras video recording will be supported. The concurrent camera preview
* stream will be shared with video capture and record the concurrent cameras as a whole. The
- * [LayoutSettings] can be used to configure the position of each camera stream.
+ * [CompositionSettings] can be used to configure the position of each camera stream.
*
* If we want to open concurrent physical cameras, which are two front cameras or two back
* cameras, the device needs to support physical cameras and the capability could be checked via
@@ -381,8 +381,8 @@
lifecycleOwner,
cameraSelector,
null,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
viewPort,
effects,
*useCases.toTypedArray<UseCase>()
@@ -446,8 +446,8 @@
firstCameraConfig.lifecycleOwner,
firstCameraConfig.cameraSelector,
secondCameraConfig.cameraSelector,
- firstCameraConfig.layoutSettings,
- secondCameraConfig.layoutSettings,
+ firstCameraConfig.compositionSettings,
+ secondCameraConfig.compositionSettings,
firstCameraConfig.useCaseGroup.viewPort,
firstCameraConfig.useCaseGroup.effects,
*firstCameraConfig.useCaseGroup.useCases.toTypedArray<UseCase>(),
@@ -460,8 +460,8 @@
config!!.lifecycleOwner,
config.cameraSelector,
null,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
config.useCaseGroup.viewPort,
config.useCaseGroup.effects,
*config.useCaseGroup.useCases.toTypedArray<UseCase>()
@@ -530,8 +530,8 @@
* @param primaryCameraSelector The primary camera selector which determines the camera to use
* for set of use cases.
* @param secondaryCameraSelector The secondary camera selector in dual camera case.
- * @param primaryLayoutSettings The layout settings for the primary camera.
- * @param secondaryLayoutSettings The layout settings for the secondary camera.
+ * @param primaryCompositionSettings The composition settings for the primary camera.
+ * @param secondaryCompositionSettings The composition settings for the secondary camera.
* @param viewPort The viewPort which represents the visible camera sensor rect.
* @param effects The effects applied to the camera outputs.
* @param useCases The use cases to bind to a lifecycle.
@@ -548,8 +548,8 @@
lifecycleOwner: LifecycleOwner,
primaryCameraSelector: CameraSelector,
secondaryCameraSelector: CameraSelector?,
- primaryLayoutSettings: LayoutSettings,
- secondaryLayoutSettings: LayoutSettings,
+ primaryCompositionSettings: CompositionSettings,
+ secondaryCompositionSettings: CompositionSettings,
viewPort: ViewPort?,
effects: List<CameraEffect?>,
vararg useCases: UseCase?
@@ -611,8 +611,8 @@
secondaryCameraInternal,
primaryRestrictedCameraInfo,
secondaryRestrictedCameraInfo,
- primaryLayoutSettings,
- secondaryLayoutSettings,
+ primaryCompositionSettings,
+ secondaryCompositionSettings,
mCameraX!!.cameraFactory.cameraCoordinator,
mCameraX!!.cameraDeviceSurfaceManager,
mCameraX!!.defaultConfigFactory
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
index dd5fe93..418e14e 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
@@ -53,8 +53,8 @@
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraX;
import androidx.camera.core.CameraXConfig;
+import androidx.camera.core.CompositionSettings;
import androidx.camera.core.ExperimentalRetryPolicy;
-import androidx.camera.core.LayoutSettings;
import androidx.camera.core.Logger;
import androidx.camera.core.RetryPolicy;
import androidx.camera.core.UseCase;
@@ -633,8 +633,8 @@
null,
new RestrictedCameraInfo(camera.getCameraInfoInternal(), cameraConfig),
null,
- LayoutSettings.DEFAULT,
- LayoutSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
+ CompositionSettings.DEFAULT,
cameraCoordinator,
cameraX.getCameraDeviceSurfaceManager(),
cameraX.getDefaultConfigFactory());
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/ScreenFlashView.java b/camera/camera-view/src/main/java/androidx/camera/view/ScreenFlashView.java
index a42bd1b..529e630 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/ScreenFlashView.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/ScreenFlashView.java
@@ -19,6 +19,8 @@
import static androidx.camera.core.ImageCapture.FLASH_MODE_SCREEN;
import static androidx.camera.core.impl.utils.Threads.checkMainThread;
+import android.animation.Animator;
+import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
@@ -29,6 +31,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
import androidx.annotation.UiThread;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCapture.ScreenFlash;
@@ -58,8 +61,8 @@
* {@link PreviewView} does not encompass the full screen, users may want to use this view
* separately so that whole screen can be encompassed during screen flash operation.
*
+ * @see #getScreenFlash
* @see ImageCapture#FLASH_MODE_SCREEN
- * @see PreviewView#getScreenFlash
*/
public final class ScreenFlashView extends View {
private static final String TAG = "ScreenFlashView";
@@ -67,6 +70,9 @@
private Window mScreenFlashWindow;
private ImageCapture.ScreenFlash mScreenFlash;
+ /** The timeout in seconds for the visibility animation at {@link ScreenFlash#apply}. */
+ private static final long ANIMATION_DURATION_MILLIS = 1000;
+
@UiThread
public ScreenFlashView(@NonNull Context context) {
this(context, null);
@@ -80,7 +86,7 @@
@UiThread
public ScreenFlashView(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
+ this(context, attrs, defStyleAttr, 0);
}
@UiThread
@@ -134,7 +140,7 @@
return;
}
mCameraController.setScreenFlashUiInfo(new ScreenFlashUiInfo(
- ScreenFlashUiInfo.ProviderType.SCREEN_FLASH_VIEW, control));
+ ScreenFlashUiInfo.ProviderType.SCREEN_FLASH_VIEW, control));
}
/**
@@ -170,45 +176,122 @@
if (mScreenFlashWindow != window) {
mScreenFlash = window == null ? null : new ScreenFlash() {
private float mPreviousBrightness;
+ private ValueAnimator mAnimator;
@Override
public void apply(long expirationTimeMillis,
@NonNull ImageCapture.ScreenFlashListener screenFlashListener) {
Logger.d(TAG, "ScreenFlash#apply");
- setAlpha(1f);
+ mPreviousBrightness = getBrightness();
+ setBrightness(1.0f);
- // Maximize screen brightness
- WindowManager.LayoutParams layoutParam = mScreenFlashWindow.getAttributes();
- mPreviousBrightness = layoutParam.screenBrightness;
- layoutParam.screenBrightness = 1F;
- mScreenFlashWindow.setAttributes(layoutParam);
-
- screenFlashListener.onCompleted();
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ mAnimator = animateToFullOpacity(screenFlashListener::onCompleted);
}
@Override
public void clear() {
Logger.d(TAG, "ScreenFlash#clearScreenFlashUi");
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ mAnimator = null;
+ }
+
setAlpha(0f);
// Restore screen brightness
- WindowManager.LayoutParams layoutParam = mScreenFlashWindow.getAttributes();
- layoutParam.screenBrightness = mPreviousBrightness;
- mScreenFlashWindow.setAttributes(layoutParam);
+ setBrightness(mPreviousBrightness);
}
};
}
}
+ private ValueAnimator animateToFullOpacity(@Nullable Runnable onAnimationEnd) {
+ Logger.d(TAG, "animateToFullOpacity");
+
+ ValueAnimator animator = ValueAnimator.ofFloat(0F, 1F);
+
+ // TODO: b/355168952 - Allow users to overwrite the animation duration.
+ animator.setDuration(getVisibilityRampUpAnimationDurationMillis());
+
+ animator.addUpdateListener(animation -> {
+ Logger.d(TAG, "animateToFullOpacity: value = " + (float) animation.getAnimatedValue());
+ setAlpha((float) animation.getAnimatedValue());
+ });
+
+ animator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(@NonNull Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation) {
+ Logger.d(TAG, "ScreenFlash#apply: onAnimationEnd");
+ if (onAnimationEnd != null) {
+ onAnimationEnd.run();
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(@NonNull Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationRepeat(@NonNull Animator animation) {
+
+ }
+ });
+
+ animator.start();
+
+ return animator;
+ }
+
+ private float getBrightness() {
+ if (mScreenFlashWindow == null) {
+ Logger.e(TAG, "setBrightness: mScreenFlashWindow is null!");
+ return Float.NaN;
+ }
+
+ WindowManager.LayoutParams layoutParam = mScreenFlashWindow.getAttributes();
+ return layoutParam.screenBrightness;
+ }
+
+ private void setBrightness(float value) {
+ if (mScreenFlashWindow == null) {
+ Logger.e(TAG, "setBrightness: mScreenFlashWindow is null!");
+ return;
+ }
+
+ if (Float.isNaN(value)) {
+ Logger.e(TAG, "setBrightness: value is NaN!");
+ return;
+ }
+
+ WindowManager.LayoutParams layoutParam = mScreenFlashWindow.getAttributes();
+ layoutParam.screenBrightness = value;
+ mScreenFlashWindow.setAttributes(layoutParam);
+ Logger.d(TAG, "Brightness set to " + layoutParam.screenBrightness);
+ }
+
/**
* Returns an {@link ScreenFlash} implementation based on the {@link Window} instance
* set via {@link #setScreenFlashWindow(Window)}.
*
* <p> When {@link ScreenFlash#apply(long, ImageCapture.ScreenFlashListener)} is invoked,
- * this view becomes fully visible and screen brightness is maximized using the provided
- * {@code Window}. The default color of the overlay view is {@link Color#WHITE}. To change
+ * this view becomes fully visible gradually with an animation and screen brightness is
+ * maximized using the provided {@code Window}. Since brightness change of the display happens
+ * asynchronously and may take some time to be completed, the animation to ramp up visibility
+ * may require a duration of sufficient delay (decided internally) before
+ * {@link ImageCapture.ScreenFlashListener#onCompleted()} is invoked.
+ *
+ * <p> The default color of the overlay view is {@link Color#WHITE}. To change
* the color, use {@link #setBackgroundColor(int)}.
*
* <p> When {@link ScreenFlash#clear()} is invoked, the view
@@ -219,11 +302,23 @@
* Window} is set or none set at all, a null value will be returned by this method.
*
* @return A simple {@link ScreenFlash} implementation, or null value if a non-null
- * {@code Window} instance hasn't been set.
+ * {@code Window} instance hasn't been set.
*/
@UiThread
@Nullable
public ScreenFlash getScreenFlash() {
return mScreenFlash;
}
+
+ /**
+ * Returns the duration of the visibility ramp-up animation.
+ *
+ * <p> This is currently used in {@link ScreenFlash#apply}.
+ *
+ * @see #getScreenFlash()
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public long getVisibilityRampUpAnimationDurationMillis() {
+ return ANIMATION_DURATION_MILLIS;
+ }
}
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/ScreenFlashViewTest.kt b/camera/camera-view/src/test/java/androidx/camera/view/ScreenFlashViewTest.kt
index 981552b..510c632 100644
--- a/camera/camera-view/src/test/java/androidx/camera/view/ScreenFlashViewTest.kt
+++ b/camera/camera-view/src/test/java/androidx/camera/view/ScreenFlashViewTest.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.os.Build
+import android.os.Looper.getMainLooper
import android.view.Window
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
@@ -26,12 +27,14 @@
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Assume
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
+import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
import org.robolectric.shadows.ShadowWindow
@@ -98,13 +101,29 @@
}
@Test
- fun isFullyVisible_whenScreenFlashApplyInvoked() {
+ fun isNotVisibleImmediately_whenScreenFlashApplyInvoked() {
val screenFlash = getScreenFlashAfterSettingWindow(true)
screenFlash!!.apply(
System.currentTimeMillis() +
TimeUnit.SECONDS.toMillis(ImageCapture.SCREEN_FLASH_UI_APPLY_TIMEOUT_SECONDS),
noOpListener,
)
+ assertThat(screenFlashView.alpha).isEqualTo(0f)
+ }
+
+ @Test
+ fun isFullyVisibleAfterAnimationDuration_whenScreenFlashApplyInvoked() = runBlocking {
+ val screenFlash = getScreenFlashAfterSettingWindow(true)
+ screenFlash!!.apply(
+ System.currentTimeMillis() +
+ TimeUnit.SECONDS.toMillis(ImageCapture.SCREEN_FLASH_UI_APPLY_TIMEOUT_SECONDS),
+ noOpListener,
+ )
+ shadowOf(getMainLooper())
+ .idleFor(
+ screenFlashView.visibilityRampUpAnimationDurationMillis + 1,
+ TimeUnit.MILLISECONDS
+ )
assertThat(screenFlashView.alpha).isEqualTo(1f)
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt
index f45d709..73dfc42 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt
@@ -141,11 +141,6 @@
@Test
fun canSubmitSupportedAeTargetFpsRanges_whenTargetFrameRateSetToPreviewOnly() = runBlocking {
assumeTrue(
- "TODO(b/331900702): Enable when the bug is fixed at camera-pipe",
- implName != CameraPipeConfig::class.simpleName
- )
-
- assumeTrue(
"TODO(b/332235883): Enable for legacy when the bug is resolved",
!isHwLevelLegacy()
)
@@ -184,11 +179,6 @@
fun canSubmitSupportedAeTargetFpsRanges_whenTargetFrameRateSetToVideoCaptureOnly() =
runBlocking {
assumeTrue(
- "TODO(b/331900702): Enable when the bug is fixed at camera-pipe",
- implName != CameraPipeConfig::class.simpleName
- )
-
- assumeTrue(
"TODO(b/332235883): Enable for legacy when the bug is resolved",
!isHwLevelLegacy()
)
@@ -233,11 +223,6 @@
@Test
fun canSetAeTargetFpsRangeWithCamera2Interop() = runBlocking {
assumeTrue(
- "TODO(b/331900702): Enable when the bug is fixed at camera-pipe",
- implName != CameraPipeConfig::class.simpleName
- )
-
- assumeTrue(
"TODO(b/332235883): Enable for legacy when the bug is resolved",
!isHwLevelLegacy()
)
@@ -292,11 +277,6 @@
@Test
fun canOverwriteFpsRangeWithCamera2Interop_whenAnotherSetViaSetTargetFrameRate() = runBlocking {
assumeTrue(
- "TODO(b/331900702): Enable when the bug is fixed at camera-pipe",
- implName != CameraPipeConfig::class.simpleName
- )
-
- assumeTrue(
"TODO(b/332235883): Enable for legacy when the bug is resolved",
!isHwLevelLegacy()
)
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
index 35a4e1f..53eb0a0 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
@@ -68,13 +68,13 @@
import androidx.camera.core.CameraControl;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraSelector;
+import androidx.camera.core.CompositionSettings;
import androidx.camera.core.ConcurrentCamera;
import androidx.camera.core.ConcurrentCamera.SingleCameraConfig;
import androidx.camera.core.DynamicRange;
import androidx.camera.core.ExperimentalCameraInfo;
import androidx.camera.core.ExperimentalMirrorMode;
import androidx.camera.core.FocusMeteringAction;
-import androidx.camera.core.LayoutSettings;
import androidx.camera.core.MeteringPoint;
import androidx.camera.core.MirrorMode;
import androidx.camera.core.Preview;
@@ -483,23 +483,19 @@
SingleCameraConfig primary = new SingleCameraConfig(
cameraSelectorPrimary,
useCaseGroup,
- new LayoutSettings.Builder()
+ new CompositionSettings.Builder()
.setAlpha(1.0f)
- .setOffsetX(0.0f)
- .setOffsetY(0.0f)
- .setWidth(1.0f)
- .setHeight(1.0f)
+ .setOffset(0.0f, 0.0f)
+ .setScale(1.0f, 1.0f)
.build(),
lifecycleOwner);
SingleCameraConfig secondary = new SingleCameraConfig(
cameraSelectorSecondary,
useCaseGroup,
- new LayoutSettings.Builder()
+ new CompositionSettings.Builder()
.setAlpha(1.0f)
- .setOffsetX(-0.3f)
- .setOffsetY(-0.4f)
- .setWidth(0.3f)
- .setHeight(0.3f)
+ .setOffset(-0.3f, -0.4f)
+ .setScale(0.3f, 0.3f)
.build(),
lifecycleOwner);
cameraProvider.bindToLifecycle(ImmutableList.of(primary, secondary));
diff --git a/camera/integration-tests/timingtestapp/build.gradle b/camera/integration-tests/timingtestapp/build.gradle
index 4a1f62f..d3e0f94 100644
--- a/camera/integration-tests/timingtestapp/build.gradle
+++ b/camera/integration-tests/timingtestapp/build.gradle
@@ -58,9 +58,9 @@
implementation("androidx.fragment:fragment-ktx:1.3.0")
implementation("androidx.appcompat:appcompat:1.1.0")
implementation("androidx.collection:collection:1.4.2")
- implementation("androidx.preference:preference:1.1.0")
+ implementation("androidx.preference:preference:1.2.1")
implementation("androidx.exifinterface:exifinterface:1.0.0")
- implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.2.0")
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4")
implementation(libs.constraintLayout)
implementation(libs.kotlinStdlib)
implementation(libs.kotlinCoroutinesAndroid)
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CustomLifecycle.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CustomLifecycle.kt
index 87f2448..5461b40 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CustomLifecycle.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CustomLifecycle.kt
@@ -36,9 +36,7 @@
lifecycleRegistry.currentState = Lifecycle.State.CREATED
}
- override fun getLifecycle(): Lifecycle {
- return lifecycleRegistry
- }
+ override val lifecycle: Lifecycle = lifecycleRegistry
fun start() {
if (Looper.myLooper() != mainHandler.looper) {
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt
index 3c7762a..570146b 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt
@@ -157,7 +157,7 @@
// Human readable report
val humanReadableReportObserver =
- Observer<String> { newReport -> binding.textLog.text = newReport ?: "" }
+ Observer<String> { newReport -> binding.textLog.text = newReport }
camViewModel.getHumanReadableReport().observe(this, humanReadableReportObserver)
}
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MultiTestSettingsFragment.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MultiTestSettingsFragment.kt
index ddcb57f..6bb1454 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MultiTestSettingsFragment.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MultiTestSettingsFragment.kt
@@ -32,11 +32,11 @@
override fun onResume() {
super.onResume()
- preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
+ preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
}
override fun onPause() {
super.onPause()
- preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
+ preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
}
}
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SingleTestSettingsFragment.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SingleTestSettingsFragment.kt
index 55dee0e..c4ace27 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SingleTestSettingsFragment.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SingleTestSettingsFragment.kt
@@ -54,12 +54,12 @@
override fun onResume() {
super.onResume()
- preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
+ preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
}
override fun onPause() {
super.onPause()
- preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
+ preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
}
/**
diff --git a/collection/collection/api/current.txt b/collection/collection/api/current.txt
index 88bc7b3..e8a0b35 100644
--- a/collection/collection/api/current.txt
+++ b/collection/collection/api/current.txt
@@ -1746,6 +1746,48 @@
method public int trim();
}
+ public final class MutableOrderedScatterSet<E> extends androidx.collection.OrderedScatterSet<E> {
+ ctor public MutableOrderedScatterSet();
+ ctor public MutableOrderedScatterSet(optional int initialCapacity);
+ method public boolean add(E element);
+ method public boolean addAll(androidx.collection.ObjectList<E> elements);
+ method public boolean addAll(androidx.collection.OrderedScatterSet<E> elements);
+ method public boolean addAll(androidx.collection.ScatterSet<E> elements);
+ method public boolean addAll(E[] elements);
+ method public boolean addAll(Iterable<? extends E> elements);
+ method public boolean addAll(kotlin.sequences.Sequence<? extends E> elements);
+ method public java.util.Set<E> asMutableSet();
+ method public void clear();
+ method public operator void minusAssign(androidx.collection.ObjectList<E> elements);
+ method public operator void minusAssign(androidx.collection.OrderedScatterSet<E> elements);
+ method public operator void minusAssign(androidx.collection.ScatterSet<E> elements);
+ method public operator void minusAssign(E element);
+ method public operator void minusAssign(E[] elements);
+ method public operator void minusAssign(Iterable<? extends E> elements);
+ method public operator void minusAssign(kotlin.sequences.Sequence<? extends E> elements);
+ method public operator void plusAssign(androidx.collection.ObjectList<E> elements);
+ method public operator void plusAssign(androidx.collection.OrderedScatterSet<E> elements);
+ method public operator void plusAssign(androidx.collection.ScatterSet<E> elements);
+ method public operator void plusAssign(E element);
+ method public operator void plusAssign(E[] elements);
+ method public operator void plusAssign(Iterable<? extends E> elements);
+ method public operator void plusAssign(kotlin.sequences.Sequence<? extends E> elements);
+ method public boolean remove(E element);
+ method public boolean removeAll(androidx.collection.ObjectList<E> elements);
+ method public boolean removeAll(androidx.collection.OrderedScatterSet<E> elements);
+ method public boolean removeAll(androidx.collection.ScatterSet<E> elements);
+ method public boolean removeAll(E[] elements);
+ method public boolean removeAll(Iterable<? extends E> elements);
+ method public boolean removeAll(kotlin.sequences.Sequence<? extends E> elements);
+ method public inline void removeIf(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public boolean retainAll(androidx.collection.OrderedScatterSet<E> elements);
+ method public boolean retainAll(androidx.collection.ScatterSet<E> elements);
+ method public boolean retainAll(java.util.Collection<? extends E> elements);
+ method public boolean retainAll(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method @IntRange(from=0L) public int trim();
+ method public void trimToSize(int maxSize);
+ }
+
public final class MutableScatterMap<K, V> extends androidx.collection.ScatterMap<K,V> {
ctor public MutableScatterMap();
ctor public MutableScatterMap(optional int initialCapacity);
@@ -1783,6 +1825,7 @@
ctor public MutableScatterSet(optional int initialCapacity);
method public boolean add(E element);
method public boolean addAll(androidx.collection.ObjectList<E> elements);
+ method public boolean addAll(androidx.collection.OrderedScatterSet<E> elements);
method public boolean addAll(androidx.collection.ScatterSet<E> elements);
method public boolean addAll(E[] elements);
method public boolean addAll(Iterable<? extends E> elements);
@@ -1790,12 +1833,14 @@
method public java.util.Set<E> asMutableSet();
method public void clear();
method public operator void minusAssign(androidx.collection.ObjectList<E> elements);
+ method public operator void minusAssign(androidx.collection.OrderedScatterSet<E> elements);
method public operator void minusAssign(androidx.collection.ScatterSet<E> elements);
method public operator void minusAssign(E element);
method public operator void minusAssign(E[] elements);
method public operator void minusAssign(Iterable<? extends E> elements);
method public operator void minusAssign(kotlin.sequences.Sequence<? extends E> elements);
method public operator void plusAssign(androidx.collection.ObjectList<E> elements);
+ method public operator void plusAssign(androidx.collection.OrderedScatterSet<E> elements);
method public operator void plusAssign(androidx.collection.ScatterSet<E> elements);
method public operator void plusAssign(E element);
method public operator void plusAssign(E[] elements);
@@ -1803,11 +1848,16 @@
method public operator void plusAssign(kotlin.sequences.Sequence<? extends E> elements);
method public boolean remove(E element);
method public boolean removeAll(androidx.collection.ObjectList<E> elements);
+ method public boolean removeAll(androidx.collection.OrderedScatterSet<E> elements);
method public boolean removeAll(androidx.collection.ScatterSet<E> elements);
method public boolean removeAll(E[] elements);
method public boolean removeAll(Iterable<? extends E> elements);
method public boolean removeAll(kotlin.sequences.Sequence<? extends E> elements);
method public inline void removeIf(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public boolean retainAll(androidx.collection.OrderedScatterSet<E> elements);
+ method public boolean retainAll(androidx.collection.ScatterSet<E> elements);
+ method public boolean retainAll(java.util.Collection<? extends E> elements);
+ method public boolean retainAll(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
method @IntRange(from=0L) public int trim();
}
@@ -2034,6 +2084,53 @@
method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3, K key4, long value4, K key5, long value5);
}
+ public abstract sealed class OrderedScatterSet<E> {
+ method public final inline boolean all(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final boolean any();
+ method public final inline boolean any(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final java.util.Set<E> asSet();
+ method public final operator boolean contains(E element);
+ method @IntRange(from=0L) public final int count();
+ method @IntRange(from=0L) public final inline int count(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final E first();
+ method public final E first(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final inline E? firstOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final inline void forEach(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+ method public final inline void forEachReverse(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+ method @IntRange(from=0L) public final int getCapacity();
+ method @IntRange(from=0L) public final int getSize();
+ method public final boolean isEmpty();
+ method public final boolean isNotEmpty();
+ method public final String joinToString();
+ method public final String joinToString(optional CharSequence separator);
+ method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+ method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+ method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+ method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+ method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, optional kotlin.jvm.functions.Function1<? super E,? extends java.lang.CharSequence>? transform);
+ method public final E last();
+ method public final E last(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final inline E? lastOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final boolean none();
+ method public final inline java.util.List<E> toList();
+ property @IntRange(from=0L) public final int capacity;
+ property @IntRange(from=0L) public final int size;
+ }
+
+ public final class OrderedScatterSetKt {
+ method public static <E> androidx.collection.OrderedScatterSet<E> emptyOrderedScatterSet();
+ method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf();
+ method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E element1);
+ method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E element1, E element2);
+ method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E element1, E element2, E element3);
+ method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E... elements);
+ method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf();
+ method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E element1);
+ method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E element1, E element2);
+ method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E element1, E element2, E element3);
+ method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E... elements);
+ }
+
public abstract sealed class ScatterMap<K, V> {
method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Boolean> predicate);
method public final boolean any();
diff --git a/collection/collection/api/restricted_current.txt b/collection/collection/api/restricted_current.txt
index de5f4d1..509b164 100644
--- a/collection/collection/api/restricted_current.txt
+++ b/collection/collection/api/restricted_current.txt
@@ -1835,6 +1835,49 @@
method public int trim();
}
+ public final class MutableOrderedScatterSet<E> extends androidx.collection.OrderedScatterSet<E> {
+ ctor public MutableOrderedScatterSet();
+ ctor public MutableOrderedScatterSet(optional int initialCapacity);
+ method public boolean add(E element);
+ method public boolean addAll(androidx.collection.ObjectList<E> elements);
+ method public boolean addAll(androidx.collection.OrderedScatterSet<E> elements);
+ method public boolean addAll(androidx.collection.ScatterSet<E> elements);
+ method public boolean addAll(E[] elements);
+ method public boolean addAll(Iterable<? extends E> elements);
+ method public boolean addAll(kotlin.sequences.Sequence<? extends E> elements);
+ method public java.util.Set<E> asMutableSet();
+ method public void clear();
+ method public operator void minusAssign(androidx.collection.ObjectList<E> elements);
+ method public operator void minusAssign(androidx.collection.OrderedScatterSet<E> elements);
+ method public operator void minusAssign(androidx.collection.ScatterSet<E> elements);
+ method public operator void minusAssign(E element);
+ method public operator void minusAssign(E[] elements);
+ method public operator void minusAssign(Iterable<? extends E> elements);
+ method public operator void minusAssign(kotlin.sequences.Sequence<? extends E> elements);
+ method public operator void plusAssign(androidx.collection.ObjectList<E> elements);
+ method public operator void plusAssign(androidx.collection.OrderedScatterSet<E> elements);
+ method public operator void plusAssign(androidx.collection.ScatterSet<E> elements);
+ method public operator void plusAssign(E element);
+ method public operator void plusAssign(E[] elements);
+ method public operator void plusAssign(Iterable<? extends E> elements);
+ method public operator void plusAssign(kotlin.sequences.Sequence<? extends E> elements);
+ method public boolean remove(E element);
+ method public boolean removeAll(androidx.collection.ObjectList<E> elements);
+ method public boolean removeAll(androidx.collection.OrderedScatterSet<E> elements);
+ method public boolean removeAll(androidx.collection.ScatterSet<E> elements);
+ method public boolean removeAll(E[] elements);
+ method public boolean removeAll(Iterable<? extends E> elements);
+ method public boolean removeAll(kotlin.sequences.Sequence<? extends E> elements);
+ method @kotlin.PublishedApi internal void removeElementAt(int index);
+ method public inline void removeIf(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public boolean retainAll(androidx.collection.OrderedScatterSet<E> elements);
+ method public boolean retainAll(androidx.collection.ScatterSet<E> elements);
+ method public boolean retainAll(java.util.Collection<? extends E> elements);
+ method public boolean retainAll(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method @IntRange(from=0L) public int trim();
+ method public void trimToSize(int maxSize);
+ }
+
public final class MutableScatterMap<K, V> extends androidx.collection.ScatterMap<K,V> {
ctor public MutableScatterMap();
ctor public MutableScatterMap(optional int initialCapacity);
@@ -1874,6 +1917,7 @@
ctor public MutableScatterSet(optional int initialCapacity);
method public boolean add(E element);
method public boolean addAll(androidx.collection.ObjectList<E> elements);
+ method public boolean addAll(androidx.collection.OrderedScatterSet<E> elements);
method public boolean addAll(androidx.collection.ScatterSet<E> elements);
method public boolean addAll(E[] elements);
method public boolean addAll(Iterable<? extends E> elements);
@@ -1881,12 +1925,14 @@
method public java.util.Set<E> asMutableSet();
method public void clear();
method public operator void minusAssign(androidx.collection.ObjectList<E> elements);
+ method public operator void minusAssign(androidx.collection.OrderedScatterSet<E> elements);
method public operator void minusAssign(androidx.collection.ScatterSet<E> elements);
method public operator void minusAssign(E element);
method public operator void minusAssign(E[] elements);
method public operator void minusAssign(Iterable<? extends E> elements);
method public operator void minusAssign(kotlin.sequences.Sequence<? extends E> elements);
method public operator void plusAssign(androidx.collection.ObjectList<E> elements);
+ method public operator void plusAssign(androidx.collection.OrderedScatterSet<E> elements);
method public operator void plusAssign(androidx.collection.ScatterSet<E> elements);
method public operator void plusAssign(E element);
method public operator void plusAssign(E[] elements);
@@ -1894,12 +1940,17 @@
method public operator void plusAssign(kotlin.sequences.Sequence<? extends E> elements);
method public boolean remove(E element);
method public boolean removeAll(androidx.collection.ObjectList<E> elements);
+ method public boolean removeAll(androidx.collection.OrderedScatterSet<E> elements);
method public boolean removeAll(androidx.collection.ScatterSet<E> elements);
method public boolean removeAll(E[] elements);
method public boolean removeAll(Iterable<? extends E> elements);
method public boolean removeAll(kotlin.sequences.Sequence<? extends E> elements);
method @kotlin.PublishedApi internal void removeElementAt(int index);
method public inline void removeIf(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public boolean retainAll(androidx.collection.OrderedScatterSet<E> elements);
+ method public boolean retainAll(androidx.collection.ScatterSet<E> elements);
+ method public boolean retainAll(java.util.Collection<? extends E> elements);
+ method public boolean retainAll(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
method @IntRange(from=0L) public int trim();
}
@@ -2143,6 +2194,60 @@
method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3, K key4, long value4, K key5, long value5);
}
+ public abstract sealed class OrderedScatterSet<E> {
+ method public final inline boolean all(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final boolean any();
+ method public final inline boolean any(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final java.util.Set<E> asSet();
+ method public final operator boolean contains(E element);
+ method @IntRange(from=0L) public final int count();
+ method @IntRange(from=0L) public final inline int count(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final E first();
+ method public final E first(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final inline E? firstOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final inline void forEach(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+ method public final inline void forEachReverse(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+ method @IntRange(from=0L) public final int getCapacity();
+ method @IntRange(from=0L) public final int getSize();
+ method public final boolean isEmpty();
+ method public final boolean isNotEmpty();
+ method public final String joinToString();
+ method public final String joinToString(optional CharSequence separator);
+ method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+ method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+ method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+ method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+ method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, optional kotlin.jvm.functions.Function1<? super E,? extends java.lang.CharSequence>? transform);
+ method public final E last();
+ method public final E last(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final inline E? lastOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final boolean none();
+ method public final inline java.util.List<E> toList();
+ method @kotlin.PublishedApi internal final inline void unorderedForEach(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+ method @kotlin.PublishedApi internal final inline void unorderedForEachIndex(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+ property @IntRange(from=0L) public final int capacity;
+ property @IntRange(from=0L) public final int size;
+ field @kotlin.PublishedApi internal Object?[] elements;
+ field @kotlin.PublishedApi internal int head;
+ field @kotlin.PublishedApi internal long[] metadata;
+ field @kotlin.PublishedApi internal long[] nodes;
+ field @kotlin.PublishedApi internal int tail;
+ }
+
+ public final class OrderedScatterSetKt {
+ method public static <E> androidx.collection.OrderedScatterSet<E> emptyOrderedScatterSet();
+ method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf();
+ method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E element1);
+ method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E element1, E element2);
+ method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E element1, E element2, E element3);
+ method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E... elements);
+ method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf();
+ method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E element1);
+ method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E element1, E element2);
+ method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E element1, E element2, E element3);
+ method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E... elements);
+ }
+
public abstract sealed class ScatterMap<K, V> {
method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Boolean> predicate);
method public final boolean any();
@@ -2296,6 +2401,11 @@
field @kotlin.PublishedApi internal Object?[] values;
}
+ public final class SieveCacheKt {
+ field @kotlin.PublishedApi internal static final int NodeInvalidLink = 2147483647; // 0x7fffffff
+ field @kotlin.PublishedApi internal static final long NodeLinkMask = 2147483647L; // 0x7fffffffL
+ }
+
public class SimpleArrayMap<K, V> {
ctor public SimpleArrayMap();
ctor public SimpleArrayMap(androidx.collection.SimpleArrayMap<? extends K,? extends V>? map);
diff --git a/collection/collection/bcv/native/current.txt b/collection/collection/bcv/native/current.txt
index e63db68..a89ea397 100644
--- a/collection/collection/bcv/native/current.txt
+++ b/collection/collection/bcv/native/current.txt
@@ -313,10 +313,52 @@
final inline fun plusAssign(androidx.collection/ObjectLongMap<#A>) // androidx.collection/MutableObjectLongMap.plusAssign|plusAssign(androidx.collection.ObjectLongMap<1:0>){}[0]
final inline fun removeIf(kotlin/Function2<#A, kotlin/Long, kotlin/Boolean>) // androidx.collection/MutableObjectLongMap.removeIf|removeIf(kotlin.Function2<1:0,kotlin.Long,kotlin.Boolean>){}[0]
}
+final class <#A: kotlin/Any?> androidx.collection/MutableOrderedScatterSet : androidx.collection/OrderedScatterSet<#A> { // androidx.collection/MutableOrderedScatterSet|null[0]
+ constructor <init>(kotlin/Int =...) // androidx.collection/MutableOrderedScatterSet.<init>|<init>(kotlin.Int){}[0]
+ final fun add(#A): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.add|add(1:0){}[0]
+ final fun addAll(androidx.collection/ObjectList<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.addAll|addAll(androidx.collection.ObjectList<1:0>){}[0]
+ final fun addAll(androidx.collection/OrderedScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.addAll|addAll(androidx.collection.OrderedScatterSet<1:0>){}[0]
+ final fun addAll(androidx.collection/ScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.addAll|addAll(androidx.collection.ScatterSet<1:0>){}[0]
+ final fun addAll(kotlin.collections/Iterable<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.addAll|addAll(kotlin.collections.Iterable<1:0>){}[0]
+ final fun addAll(kotlin.sequences/Sequence<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.addAll|addAll(kotlin.sequences.Sequence<1:0>){}[0]
+ final fun addAll(kotlin/Array<out #A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.addAll|addAll(kotlin.Array<out|1:0>){}[0]
+ final fun asMutableSet(): kotlin.collections/MutableSet<#A> // androidx.collection/MutableOrderedScatterSet.asMutableSet|asMutableSet(){}[0]
+ final fun clear() // androidx.collection/MutableOrderedScatterSet.clear|clear(){}[0]
+ final fun minusAssign(#A) // androidx.collection/MutableOrderedScatterSet.minusAssign|minusAssign(1:0){}[0]
+ final fun minusAssign(androidx.collection/ObjectList<#A>) // androidx.collection/MutableOrderedScatterSet.minusAssign|minusAssign(androidx.collection.ObjectList<1:0>){}[0]
+ final fun minusAssign(androidx.collection/OrderedScatterSet<#A>) // androidx.collection/MutableOrderedScatterSet.minusAssign|minusAssign(androidx.collection.OrderedScatterSet<1:0>){}[0]
+ final fun minusAssign(androidx.collection/ScatterSet<#A>) // androidx.collection/MutableOrderedScatterSet.minusAssign|minusAssign(androidx.collection.ScatterSet<1:0>){}[0]
+ final fun minusAssign(kotlin.collections/Iterable<#A>) // androidx.collection/MutableOrderedScatterSet.minusAssign|minusAssign(kotlin.collections.Iterable<1:0>){}[0]
+ final fun minusAssign(kotlin.sequences/Sequence<#A>) // androidx.collection/MutableOrderedScatterSet.minusAssign|minusAssign(kotlin.sequences.Sequence<1:0>){}[0]
+ final fun minusAssign(kotlin/Array<out #A>) // androidx.collection/MutableOrderedScatterSet.minusAssign|minusAssign(kotlin.Array<out|1:0>){}[0]
+ final fun plusAssign(#A) // androidx.collection/MutableOrderedScatterSet.plusAssign|plusAssign(1:0){}[0]
+ final fun plusAssign(androidx.collection/ObjectList<#A>) // androidx.collection/MutableOrderedScatterSet.plusAssign|plusAssign(androidx.collection.ObjectList<1:0>){}[0]
+ final fun plusAssign(androidx.collection/OrderedScatterSet<#A>) // androidx.collection/MutableOrderedScatterSet.plusAssign|plusAssign(androidx.collection.OrderedScatterSet<1:0>){}[0]
+ final fun plusAssign(androidx.collection/ScatterSet<#A>) // androidx.collection/MutableOrderedScatterSet.plusAssign|plusAssign(androidx.collection.ScatterSet<1:0>){}[0]
+ final fun plusAssign(kotlin.collections/Iterable<#A>) // androidx.collection/MutableOrderedScatterSet.plusAssign|plusAssign(kotlin.collections.Iterable<1:0>){}[0]
+ final fun plusAssign(kotlin.sequences/Sequence<#A>) // androidx.collection/MutableOrderedScatterSet.plusAssign|plusAssign(kotlin.sequences.Sequence<1:0>){}[0]
+ final fun plusAssign(kotlin/Array<out #A>) // androidx.collection/MutableOrderedScatterSet.plusAssign|plusAssign(kotlin.Array<out|1:0>){}[0]
+ final fun remove(#A): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.remove|remove(1:0){}[0]
+ final fun removeAll(androidx.collection/ObjectList<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.removeAll|removeAll(androidx.collection.ObjectList<1:0>){}[0]
+ final fun removeAll(androidx.collection/OrderedScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.removeAll|removeAll(androidx.collection.OrderedScatterSet<1:0>){}[0]
+ final fun removeAll(androidx.collection/ScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.removeAll|removeAll(androidx.collection.ScatterSet<1:0>){}[0]
+ final fun removeAll(kotlin.collections/Iterable<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.removeAll|removeAll(kotlin.collections.Iterable<1:0>){}[0]
+ final fun removeAll(kotlin.sequences/Sequence<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.removeAll|removeAll(kotlin.sequences.Sequence<1:0>){}[0]
+ final fun removeAll(kotlin/Array<out #A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.removeAll|removeAll(kotlin.Array<out|1:0>){}[0]
+ final fun removeElementAt(kotlin/Int) // androidx.collection/MutableOrderedScatterSet.removeElementAt|removeElementAt(kotlin.Int){}[0]
+ final fun retainAll(androidx.collection/OrderedScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.retainAll|retainAll(androidx.collection.OrderedScatterSet<1:0>){}[0]
+ final fun retainAll(androidx.collection/ScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.retainAll|retainAll(androidx.collection.ScatterSet<1:0>){}[0]
+ final fun retainAll(kotlin.collections/Collection<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.retainAll|retainAll(kotlin.collections.Collection<1:0>){}[0]
+ final fun retainAll(kotlin/Function1<#A, kotlin/Boolean>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.retainAll|retainAll(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+ final fun trim(): kotlin/Int // androidx.collection/MutableOrderedScatterSet.trim|trim(){}[0]
+ final fun trimToSize(kotlin/Int) // androidx.collection/MutableOrderedScatterSet.trimToSize|trimToSize(kotlin.Int){}[0]
+ final inline fun removeIf(kotlin/Function1<#A, kotlin/Boolean>) // androidx.collection/MutableOrderedScatterSet.removeIf|removeIf(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+}
final class <#A: kotlin/Any?> androidx.collection/MutableScatterSet : androidx.collection/ScatterSet<#A> { // androidx.collection/MutableScatterSet|null[0]
constructor <init>(kotlin/Int =...) // androidx.collection/MutableScatterSet.<init>|<init>(kotlin.Int){}[0]
final fun add(#A): kotlin/Boolean // androidx.collection/MutableScatterSet.add|add(1:0){}[0]
final fun addAll(androidx.collection/ObjectList<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.addAll|addAll(androidx.collection.ObjectList<1:0>){}[0]
+ final fun addAll(androidx.collection/OrderedScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.addAll|addAll(androidx.collection.OrderedScatterSet<1:0>){}[0]
final fun addAll(androidx.collection/ScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.addAll|addAll(androidx.collection.ScatterSet<1:0>){}[0]
final fun addAll(kotlin.collections/Iterable<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.addAll|addAll(kotlin.collections.Iterable<1:0>){}[0]
final fun addAll(kotlin.sequences/Sequence<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.addAll|addAll(kotlin.sequences.Sequence<1:0>){}[0]
@@ -325,23 +367,30 @@
final fun clear() // androidx.collection/MutableScatterSet.clear|clear(){}[0]
final fun minusAssign(#A) // androidx.collection/MutableScatterSet.minusAssign|minusAssign(1:0){}[0]
final fun minusAssign(androidx.collection/ObjectList<#A>) // androidx.collection/MutableScatterSet.minusAssign|minusAssign(androidx.collection.ObjectList<1:0>){}[0]
+ final fun minusAssign(androidx.collection/OrderedScatterSet<#A>) // androidx.collection/MutableScatterSet.minusAssign|minusAssign(androidx.collection.OrderedScatterSet<1:0>){}[0]
final fun minusAssign(androidx.collection/ScatterSet<#A>) // androidx.collection/MutableScatterSet.minusAssign|minusAssign(androidx.collection.ScatterSet<1:0>){}[0]
final fun minusAssign(kotlin.collections/Iterable<#A>) // androidx.collection/MutableScatterSet.minusAssign|minusAssign(kotlin.collections.Iterable<1:0>){}[0]
final fun minusAssign(kotlin.sequences/Sequence<#A>) // androidx.collection/MutableScatterSet.minusAssign|minusAssign(kotlin.sequences.Sequence<1:0>){}[0]
final fun minusAssign(kotlin/Array<out #A>) // androidx.collection/MutableScatterSet.minusAssign|minusAssign(kotlin.Array<out|1:0>){}[0]
final fun plusAssign(#A) // androidx.collection/MutableScatterSet.plusAssign|plusAssign(1:0){}[0]
final fun plusAssign(androidx.collection/ObjectList<#A>) // androidx.collection/MutableScatterSet.plusAssign|plusAssign(androidx.collection.ObjectList<1:0>){}[0]
+ final fun plusAssign(androidx.collection/OrderedScatterSet<#A>) // androidx.collection/MutableScatterSet.plusAssign|plusAssign(androidx.collection.OrderedScatterSet<1:0>){}[0]
final fun plusAssign(androidx.collection/ScatterSet<#A>) // androidx.collection/MutableScatterSet.plusAssign|plusAssign(androidx.collection.ScatterSet<1:0>){}[0]
final fun plusAssign(kotlin.collections/Iterable<#A>) // androidx.collection/MutableScatterSet.plusAssign|plusAssign(kotlin.collections.Iterable<1:0>){}[0]
final fun plusAssign(kotlin.sequences/Sequence<#A>) // androidx.collection/MutableScatterSet.plusAssign|plusAssign(kotlin.sequences.Sequence<1:0>){}[0]
final fun plusAssign(kotlin/Array<out #A>) // androidx.collection/MutableScatterSet.plusAssign|plusAssign(kotlin.Array<out|1:0>){}[0]
final fun remove(#A): kotlin/Boolean // androidx.collection/MutableScatterSet.remove|remove(1:0){}[0]
final fun removeAll(androidx.collection/ObjectList<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.removeAll|removeAll(androidx.collection.ObjectList<1:0>){}[0]
+ final fun removeAll(androidx.collection/OrderedScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.removeAll|removeAll(androidx.collection.OrderedScatterSet<1:0>){}[0]
final fun removeAll(androidx.collection/ScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.removeAll|removeAll(androidx.collection.ScatterSet<1:0>){}[0]
final fun removeAll(kotlin.collections/Iterable<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.removeAll|removeAll(kotlin.collections.Iterable<1:0>){}[0]
final fun removeAll(kotlin.sequences/Sequence<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.removeAll|removeAll(kotlin.sequences.Sequence<1:0>){}[0]
final fun removeAll(kotlin/Array<out #A>): kotlin/Boolean // androidx.collection/MutableScatterSet.removeAll|removeAll(kotlin.Array<out|1:0>){}[0]
final fun removeElementAt(kotlin/Int) // androidx.collection/MutableScatterSet.removeElementAt|removeElementAt(kotlin.Int){}[0]
+ final fun retainAll(androidx.collection/OrderedScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.retainAll|retainAll(androidx.collection.OrderedScatterSet<1:0>){}[0]
+ final fun retainAll(androidx.collection/ScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.retainAll|retainAll(androidx.collection.ScatterSet<1:0>){}[0]
+ final fun retainAll(kotlin.collections/Collection<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.retainAll|retainAll(kotlin.collections.Collection<1:0>){}[0]
+ final fun retainAll(kotlin/Function1<#A, kotlin/Boolean>): kotlin/Boolean // androidx.collection/MutableScatterSet.retainAll|retainAll(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
final fun trim(): kotlin/Int // androidx.collection/MutableScatterSet.trim|trim(){}[0]
final inline fun removeIf(kotlin/Function1<#A, kotlin/Boolean>) // androidx.collection/MutableScatterSet.removeIf|removeIf(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
}
@@ -720,6 +769,10 @@
final fun <get-BitmaskLsb>(): kotlin/Long // androidx.collection/BitmaskLsb.<get-BitmaskLsb>|<get-BitmaskLsb>(){}[0]
final const val androidx.collection/BitmaskMsb // androidx.collection/BitmaskMsb|<get-BitmaskMsb>(){}[0]
final fun <get-BitmaskMsb>(): kotlin/Long // androidx.collection/BitmaskMsb.<get-BitmaskMsb>|<get-BitmaskMsb>(){}[0]
+final const val androidx.collection/NodeInvalidLink // androidx.collection/NodeInvalidLink|{}NodeInvalidLink[0]
+ final fun <get-NodeInvalidLink>(): kotlin/Int // androidx.collection/NodeInvalidLink.<get-NodeInvalidLink>|<get-NodeInvalidLink>(){}[0]
+final const val androidx.collection/NodeLinkMask // androidx.collection/NodeLinkMask|{}NodeLinkMask[0]
+ final fun <get-NodeLinkMask>(): kotlin/Long // androidx.collection/NodeLinkMask.<get-NodeLinkMask>|<get-NodeLinkMask>(){}[0]
final const val androidx.collection/Sentinel // androidx.collection/Sentinel|{}Sentinel[0]
final fun <get-Sentinel>(): kotlin/Long // androidx.collection/Sentinel.<get-Sentinel>|<get-Sentinel>(){}[0]
final fun <#A: kotlin/Any?, #B: kotlin/Any?> androidx.collection/emptyScatterMap(): androidx.collection/ScatterMap<#A, #B> // androidx.collection/emptyScatterMap|emptyScatterMap(){0§<kotlin.Any?>;1§<kotlin.Any?>}[0]
@@ -741,6 +794,7 @@
final fun <#A: kotlin/Any?> androidx.collection/emptyObjectIntMap(): androidx.collection/ObjectIntMap<#A> // androidx.collection/emptyObjectIntMap|emptyObjectIntMap(){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/emptyObjectList(): androidx.collection/ObjectList<#A> // androidx.collection/emptyObjectList|emptyObjectList(){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/emptyObjectLongMap(): androidx.collection/ObjectLongMap<#A> // androidx.collection/emptyObjectLongMap|emptyObjectLongMap(){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/emptyOrderedScatterSet(): androidx.collection/OrderedScatterSet<#A> // androidx.collection/emptyOrderedScatterSet|emptyOrderedScatterSet(){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/emptyScatterSet(): androidx.collection/ScatterSet<#A> // androidx.collection/emptyScatterSet|emptyScatterSet(){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/floatObjectMapOf(): androidx.collection/FloatObjectMap<#A> // androidx.collection/floatObjectMapOf|floatObjectMapOf(){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/floatObjectMapOf(kotlin/Float, #A): androidx.collection/FloatObjectMap<#A> // androidx.collection/floatObjectMapOf|floatObjectMapOf(kotlin.Float;0:0){0§<kotlin.Any?>}[0]
@@ -799,6 +853,11 @@
final fun <#A: kotlin/Any?> androidx.collection/mutableObjectLongMapOf(#A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long): androidx.collection/MutableObjectLongMap<#A> // androidx.collection/mutableObjectLongMapOf|mutableObjectLongMapOf(0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/mutableObjectLongMapOf(#A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long): androidx.collection/MutableObjectLongMap<#A> // androidx.collection/mutableObjectLongMapOf|mutableObjectLongMapOf(0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/mutableObjectLongMapOf(): androidx.collection/MutableObjectLongMap<#A> // androidx.collection/mutableObjectLongMapOf|mutableObjectLongMapOf(){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/mutableOrderedScatterSetOf(#A): androidx.collection/MutableOrderedScatterSet<#A> // androidx.collection/mutableOrderedScatterSetOf|mutableOrderedScatterSetOf(0:0){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/mutableOrderedScatterSetOf(#A, #A): androidx.collection/MutableOrderedScatterSet<#A> // androidx.collection/mutableOrderedScatterSetOf|mutableOrderedScatterSetOf(0:0;0:0){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/mutableOrderedScatterSetOf(#A, #A, #A): androidx.collection/MutableOrderedScatterSet<#A> // androidx.collection/mutableOrderedScatterSetOf|mutableOrderedScatterSetOf(0:0;0:0;0:0){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/mutableOrderedScatterSetOf(): androidx.collection/MutableOrderedScatterSet<#A> // androidx.collection/mutableOrderedScatterSetOf|mutableOrderedScatterSetOf(){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/mutableOrderedScatterSetOf(kotlin/Array<out #A>...): androidx.collection/MutableOrderedScatterSet<#A> // androidx.collection/mutableOrderedScatterSetOf|mutableOrderedScatterSetOf(kotlin.Array<out|0:0>...){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/mutableScatterSetOf(#A): androidx.collection/MutableScatterSet<#A> // androidx.collection/mutableScatterSetOf|mutableScatterSetOf(0:0){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/mutableScatterSetOf(#A, #A): androidx.collection/MutableScatterSet<#A> // androidx.collection/mutableScatterSetOf|mutableScatterSetOf(0:0;0:0){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/mutableScatterSetOf(#A, #A, #A): androidx.collection/MutableScatterSet<#A> // androidx.collection/mutableScatterSetOf|mutableScatterSetOf(0:0;0:0;0:0){0§<kotlin.Any?>}[0]
@@ -827,6 +886,11 @@
final fun <#A: kotlin/Any?> androidx.collection/objectLongMapOf(#A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long): androidx.collection/ObjectLongMap<#A> // androidx.collection/objectLongMapOf|objectLongMapOf(0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/objectLongMapOf(#A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long): androidx.collection/ObjectLongMap<#A> // androidx.collection/objectLongMapOf|objectLongMapOf(0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/objectLongMapOf(#A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long): androidx.collection/ObjectLongMap<#A> // androidx.collection/objectLongMapOf|objectLongMapOf(0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/orderedScatterSetOf(#A): androidx.collection/OrderedScatterSet<#A> // androidx.collection/orderedScatterSetOf|orderedScatterSetOf(0:0){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/orderedScatterSetOf(#A, #A): androidx.collection/OrderedScatterSet<#A> // androidx.collection/orderedScatterSetOf|orderedScatterSetOf(0:0;0:0){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/orderedScatterSetOf(#A, #A, #A): androidx.collection/OrderedScatterSet<#A> // androidx.collection/orderedScatterSetOf|orderedScatterSetOf(0:0;0:0;0:0){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/orderedScatterSetOf(): androidx.collection/OrderedScatterSet<#A> // androidx.collection/orderedScatterSetOf|orderedScatterSetOf(){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/orderedScatterSetOf(kotlin/Array<out #A>...): androidx.collection/OrderedScatterSet<#A> // androidx.collection/orderedScatterSetOf|orderedScatterSetOf(kotlin.Array<out|0:0>...){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/scatterSetOf(#A): androidx.collection/ScatterSet<#A> // androidx.collection/scatterSetOf|scatterSetOf(0:0){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/scatterSetOf(#A, #A): androidx.collection/ScatterSet<#A> // androidx.collection/scatterSetOf|scatterSetOf(0:0;0:0){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> androidx.collection/scatterSetOf(#A, #A, #A): androidx.collection/ScatterSet<#A> // androidx.collection/scatterSetOf|scatterSetOf(0:0;0:0;0:0){0§<kotlin.Any?>}[0]
@@ -1048,6 +1112,10 @@
final inline fun androidx.collection/mutableLongListOf(): androidx.collection/MutableLongList // androidx.collection/mutableLongListOf|mutableLongListOf(){}[0]
final inline fun androidx.collection/mutableLongListOf(kotlin/LongArray...): androidx.collection/MutableLongList // androidx.collection/mutableLongListOf|mutableLongListOf(kotlin.LongArray...){}[0]
final inline fun androidx.collection/readRawMetadata(kotlin/LongArray, kotlin/Int): kotlin/Long // androidx.collection/readRawMetadata|readRawMetadata(kotlin.LongArray;kotlin.Int){}[0]
+final val androidx.collection/nextNode // androidx.collection/nextNode|<get-nextNode>@kotlin.Long(){}[0]
+ final inline fun (kotlin/Long).<get-nextNode>(): kotlin/Int // androidx.collection/nextNode.<get-nextNode>|<get-nextNode>@kotlin.Long(){}[0]
+final val androidx.collection/previousNode // androidx.collection/previousNode|<get-previousNode>@kotlin.Long(){}[0]
+ final inline fun (kotlin/Long).<get-previousNode>(): kotlin/Int // androidx.collection/previousNode.<get-previousNode>|<get-previousNode>@kotlin.Long(){}[0]
final val androidx.collection/size // androidx.collection/size|@androidx.collection.LongSparseArray<0:0>{0§<kotlin.Any?>}size[0]
final inline fun <#A1: kotlin/Any?> (androidx.collection/LongSparseArray<#A1>).<get-size>(): kotlin/Int // androidx.collection/size.<get-size>|<get-size>@androidx.collection.LongSparseArray<0:0>(){0§<kotlin.Any?>}[0]
final val androidx.collection/size // androidx.collection/size|@androidx.collection.SparseArrayCompat<0:0>{0§<kotlin.Any?>}size[0]
@@ -1512,6 +1580,53 @@
open fun hashCode(): kotlin/Int // androidx.collection/ObjectLongMap.hashCode|hashCode(){}[0]
open fun toString(): kotlin/String // androidx.collection/ObjectLongMap.toString|toString(){}[0]
}
+sealed class <#A: kotlin/Any?> androidx.collection/OrderedScatterSet { // androidx.collection/OrderedScatterSet|null[0]
+ constructor <init>() // androidx.collection/OrderedScatterSet.<init>|<init>(){}[0]
+ final fun any(): kotlin/Boolean // androidx.collection/OrderedScatterSet.any|any(){}[0]
+ final fun asSet(): kotlin.collections/Set<#A> // androidx.collection/OrderedScatterSet.asSet|asSet(){}[0]
+ final fun contains(#A): kotlin/Boolean // androidx.collection/OrderedScatterSet.contains|contains(1:0){}[0]
+ final fun count(): kotlin/Int // androidx.collection/OrderedScatterSet.count|count(){}[0]
+ final fun first(): #A // androidx.collection/OrderedScatterSet.first|first(){}[0]
+ final fun first(kotlin/Function1<#A, kotlin/Boolean>): #A // androidx.collection/OrderedScatterSet.first|first(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+ final fun isEmpty(): kotlin/Boolean // androidx.collection/OrderedScatterSet.isEmpty|isEmpty(){}[0]
+ final fun isNotEmpty(): kotlin/Boolean // androidx.collection/OrderedScatterSet.isNotEmpty|isNotEmpty(){}[0]
+ final fun joinToString(kotlin/CharSequence =..., kotlin/CharSequence =..., kotlin/CharSequence =..., kotlin/Int =..., kotlin/CharSequence =..., kotlin/Function1<#A, kotlin/CharSequence>? =...): kotlin/String // androidx.collection/OrderedScatterSet.joinToString|joinToString(kotlin.CharSequence;kotlin.CharSequence;kotlin.CharSequence;kotlin.Int;kotlin.CharSequence;kotlin.Function1<1:0,kotlin.CharSequence>?){}[0]
+ final fun last(): #A // androidx.collection/OrderedScatterSet.last|last(){}[0]
+ final fun last(kotlin/Function1<#A, kotlin/Boolean>): #A // androidx.collection/OrderedScatterSet.last|last(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+ final fun none(): kotlin/Boolean // androidx.collection/OrderedScatterSet.none|none(){}[0]
+ final inline fun all(kotlin/Function1<#A, kotlin/Boolean>): kotlin/Boolean // androidx.collection/OrderedScatterSet.all|all(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+ final inline fun any(kotlin/Function1<#A, kotlin/Boolean>): kotlin/Boolean // androidx.collection/OrderedScatterSet.any|any(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+ final inline fun count(kotlin/Function1<#A, kotlin/Boolean>): kotlin/Int // androidx.collection/OrderedScatterSet.count|count(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+ final inline fun firstOrNull(kotlin/Function1<#A, kotlin/Boolean>): #A? // androidx.collection/OrderedScatterSet.firstOrNull|firstOrNull(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+ final inline fun forEach(kotlin/Function1<#A, kotlin/Unit>) // androidx.collection/OrderedScatterSet.forEach|forEach(kotlin.Function1<1:0,kotlin.Unit>){}[0]
+ final inline fun forEachReverse(kotlin/Function1<#A, kotlin/Unit>) // androidx.collection/OrderedScatterSet.forEachReverse|forEachReverse(kotlin.Function1<1:0,kotlin.Unit>){}[0]
+ final inline fun lastOrNull(kotlin/Function1<#A, kotlin/Boolean>): #A? // androidx.collection/OrderedScatterSet.lastOrNull|lastOrNull(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+ final inline fun toList(): kotlin.collections/List<#A> // androidx.collection/OrderedScatterSet.toList|toList(){}[0]
+ final inline fun unorderedForEach(kotlin/Function1<#A, kotlin/Unit>) // androidx.collection/OrderedScatterSet.unorderedForEach|unorderedForEach(kotlin.Function1<1:0,kotlin.Unit>){}[0]
+ final inline fun unorderedForEachIndex(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/OrderedScatterSet.unorderedForEachIndex|unorderedForEachIndex(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
+ final val capacity // androidx.collection/OrderedScatterSet.capacity|{}capacity[0]
+ final fun <get-capacity>(): kotlin/Int // androidx.collection/OrderedScatterSet.capacity.<get-capacity>|<get-capacity>(){}[0]
+ final val size // androidx.collection/OrderedScatterSet.size|{}size[0]
+ final fun <get-size>(): kotlin/Int // androidx.collection/OrderedScatterSet.size.<get-size>|<get-size>(){}[0]
+ final var elements // androidx.collection/OrderedScatterSet.elements|{}elements[0]
+ final fun <get-elements>(): kotlin/Array<kotlin/Any?> // androidx.collection/OrderedScatterSet.elements.<get-elements>|<get-elements>(){}[0]
+ final fun <set-elements>(kotlin/Array<kotlin/Any?>) // androidx.collection/OrderedScatterSet.elements.<set-elements>|<set-elements>(kotlin.Array<kotlin.Any?>){}[0]
+ final var head // androidx.collection/OrderedScatterSet.head|{}head[0]
+ final fun <get-head>(): kotlin/Int // androidx.collection/OrderedScatterSet.head.<get-head>|<get-head>(){}[0]
+ final fun <set-head>(kotlin/Int) // androidx.collection/OrderedScatterSet.head.<set-head>|<set-head>(kotlin.Int){}[0]
+ final var metadata // androidx.collection/OrderedScatterSet.metadata|{}metadata[0]
+ final fun <get-metadata>(): kotlin/LongArray // androidx.collection/OrderedScatterSet.metadata.<get-metadata>|<get-metadata>(){}[0]
+ final fun <set-metadata>(kotlin/LongArray) // androidx.collection/OrderedScatterSet.metadata.<set-metadata>|<set-metadata>(kotlin.LongArray){}[0]
+ final var nodes // androidx.collection/OrderedScatterSet.nodes|{}nodes[0]
+ final fun <get-nodes>(): kotlin/LongArray // androidx.collection/OrderedScatterSet.nodes.<get-nodes>|<get-nodes>(){}[0]
+ final fun <set-nodes>(kotlin/LongArray) // androidx.collection/OrderedScatterSet.nodes.<set-nodes>|<set-nodes>(kotlin.LongArray){}[0]
+ final var tail // androidx.collection/OrderedScatterSet.tail|{}tail[0]
+ final fun <get-tail>(): kotlin/Int // androidx.collection/OrderedScatterSet.tail.<get-tail>|<get-tail>(){}[0]
+ final fun <set-tail>(kotlin/Int) // androidx.collection/OrderedScatterSet.tail.<set-tail>|<set-tail>(kotlin.Int){}[0]
+ open fun equals(kotlin/Any?): kotlin/Boolean // androidx.collection/OrderedScatterSet.equals|equals(kotlin.Any?){}[0]
+ open fun hashCode(): kotlin/Int // androidx.collection/OrderedScatterSet.hashCode|hashCode(){}[0]
+ open fun toString(): kotlin/String // androidx.collection/OrderedScatterSet.toString|toString(){}[0]
+}
sealed class <#A: kotlin/Any?> androidx.collection/ScatterSet { // androidx.collection/ScatterSet|null[0]
constructor <init>() // androidx.collection/ScatterSet.<init>|<init>(){}[0]
final fun any(): kotlin/Boolean // androidx.collection/ScatterSet.any|any(){}[0]
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/OrderedScatterSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/OrderedScatterSet.kt
new file mode 100644
index 0000000..4942e42
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/OrderedScatterSet.kt
@@ -0,0 +1,1430 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+ "RedundantVisibilityModifier",
+ "KotlinRedundantDiagnosticSuppress",
+ "KotlinConstantConditions",
+ "PropertyName",
+ "ConstPropertyName",
+ "PrivatePropertyName",
+ "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import androidx.annotation.IntRange
+import androidx.collection.internal.EMPTY_OBJECTS
+import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.contract
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
+
+// Default empty set to avoid allocations
+private val EmptyOrderedScatterSet = MutableOrderedScatterSet<Any?>(0)
+
+/** Returns an empty, read-only [OrderedScatterSet]. */
+@Suppress("UNCHECKED_CAST")
+public fun <E> emptyOrderedScatterSet(): OrderedScatterSet<E> =
+ EmptyOrderedScatterSet as OrderedScatterSet<E>
+
+/** Returns an empty, read-only [OrderedScatterSet]. */
+@Suppress("UNCHECKED_CAST")
+public fun <E> orderedScatterSetOf(): OrderedScatterSet<E> =
+ EmptyOrderedScatterSet as OrderedScatterSet<E>
+
+/** Returns a new read-only [OrderedScatterSet] with only [element1] in it. */
+@Suppress("UNCHECKED_CAST")
+public fun <E> orderedScatterSetOf(element1: E): OrderedScatterSet<E> =
+ mutableOrderedScatterSetOf(element1)
+
+/** Returns a new read-only [OrderedScatterSet] with only [element1] and [element2] in it. */
+@Suppress("UNCHECKED_CAST")
+public fun <E> orderedScatterSetOf(element1: E, element2: E): OrderedScatterSet<E> =
+ mutableOrderedScatterSetOf(element1, element2)
+
+/**
+ * Returns a new read-only [OrderedScatterSet] with only [element1], [element2], and [element3] in
+ * it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <E> orderedScatterSetOf(element1: E, element2: E, element3: E): OrderedScatterSet<E> =
+ mutableOrderedScatterSetOf(element1, element2, element3)
+
+/** Returns a new read-only [OrderedScatterSet] with only [elements] in it. */
+@Suppress("UNCHECKED_CAST")
+public fun <E> orderedScatterSetOf(vararg elements: E): OrderedScatterSet<E> =
+ MutableOrderedScatterSet<E>(elements.size).apply { plusAssign(elements) }
+
+/** Returns a new [MutableOrderedScatterSet]. */
+public fun <E> mutableOrderedScatterSetOf(): MutableOrderedScatterSet<E> =
+ MutableOrderedScatterSet()
+
+/** Returns a new [MutableOrderedScatterSet] with only [element1] in it. */
+public fun <E> mutableOrderedScatterSetOf(element1: E): MutableOrderedScatterSet<E> =
+ MutableOrderedScatterSet<E>(1).apply { plusAssign(element1) }
+
+/** Returns a new [MutableOrderedScatterSet] with only [element1] and [element2] in it. */
+public fun <E> mutableOrderedScatterSetOf(element1: E, element2: E): MutableOrderedScatterSet<E> =
+ MutableOrderedScatterSet<E>(2).apply {
+ plusAssign(element1)
+ plusAssign(element2)
+ }
+
+/**
+ * Returns a new [MutableOrderedScatterSet] with only [element1], [element2], and [element3] in it.
+ */
+public fun <E> mutableOrderedScatterSetOf(
+ element1: E,
+ element2: E,
+ element3: E
+): MutableOrderedScatterSet<E> =
+ MutableOrderedScatterSet<E>(3).apply {
+ plusAssign(element1)
+ plusAssign(element2)
+ plusAssign(element3)
+ }
+
+/** Returns a new [MutableOrderedScatterSet] with the specified contents. */
+public fun <E> mutableOrderedScatterSetOf(vararg elements: E): MutableOrderedScatterSet<E> =
+ MutableOrderedScatterSet<E>(elements.size).apply { plusAssign(elements) }
+
+/**
+ * [OrderedScatterSet] is a container with a [Set]-like interface based on a flat hash table
+ * implementation that preserves the insertion order for iteration. The underlying implementation is
+ * designed to avoid all allocations on insertion, removal, retrieval, and iteration. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to accommodate newly
+ * added elements to the set.
+ *
+ * This implementation guarantees the order of the elements when iterating over them using [forEach]
+ * or the iterator provided by [asSet].
+ *
+ * Though [OrderedScatterSet] offers a read-only interface, it is always backed by a
+ * [MutableOrderedScatterSet]. Read operations alone are thread-safe. However, any mutations done
+ * through the backing [MutableScatterSet] while reading on another thread are not safe and the
+ * developer must protect the set from such changes during read operations.
+ *
+ * **Note**: when a [Set] is absolutely necessary, you can use the method [asSet] to create a thin
+ * wrapper around an [OrderedScatterSet]. Please refer to [asSet] for more details and caveats.
+ *
+ * @see [MutableOrderedScatterSet]
+ */
+public sealed class OrderedScatterSet<E> {
+ // NOTE: Our arrays are marked internal to implement inlined forEach{}
+ // The backing array for the metadata bytes contains
+ // `capacity + 1 + ClonedMetadataCount` elements, including when
+ // the set is empty (see [EmptyGroup]).
+ @PublishedApi @JvmField internal var metadata: LongArray = EmptyGroup
+
+ @PublishedApi @JvmField internal var elements: Array<Any?> = EMPTY_OBJECTS
+
+ @PublishedApi @JvmField internal var nodes: LongArray = EmptyNodes
+ @PublishedApi @JvmField internal var head: Int = NodeInvalidLink
+ @PublishedApi @JvmField internal var tail: Int = NodeInvalidLink
+
+ // We use a backing field for capacity to avoid invokevirtual calls
+ // every time we need to look at the capacity
+ @JvmField internal var _capacity: Int = 0
+
+ /**
+ * Returns the number of elements that can be stored in this set without requiring internal
+ * storage reallocation.
+ */
+ @get:IntRange(from = 0)
+ public val capacity: Int
+ get() = _capacity
+
+ // We use a backing field for capacity to avoid invokevirtual calls
+ // every time we need to look at the size
+ @JvmField internal var _size: Int = 0
+
+ /** Returns the number of elements in this set. */
+ @get:IntRange(from = 0)
+ public val size: Int
+ get() = _size
+
+ /** Returns `true` if this set has at least one element. */
+ public fun any(): Boolean = _size != 0
+
+ /** Returns `true` if this set has no elements. */
+ public fun none(): Boolean = _size == 0
+
+ /** Indicates whether this set is empty. */
+ public fun isEmpty(): Boolean = _size == 0
+
+ /** Returns `true` if this set is not empty. */
+ public fun isNotEmpty(): Boolean = _size != 0
+
+ /**
+ * Returns the first element in the collection.
+ *
+ * @throws NoSuchElementException if the collection is empty
+ */
+ public fun first(): E {
+ forEach {
+ return it
+ }
+ throw NoSuchElementException("The OrderedScatterSet is empty")
+ }
+
+ /**
+ * Returns the first element in insertion order in the collection for which [predicate] returns
+ * `true`.
+ *
+ * @param predicate called with each element until it returns `true`.
+ * @return The element for which [predicate] returns `true`.
+ * @throws NoSuchElementException if [predicate] returns `false` for all elements or the
+ * collection is empty.
+ */
+ public fun first(predicate: (element: E) -> Boolean): E {
+ contract { callsInPlace(predicate) }
+ forEach { if (predicate(it)) return it }
+ throw NoSuchElementException("Could not find a match")
+ }
+
+ /**
+ * Returns the first element in insertion order in the collection for which [predicate] returns
+ * `true` or `null` if there are no elements that match [predicate].
+ *
+ * @param predicate called with each element until it returns `true`.
+ * @return The element for which [predicate] returns `true` or `null` if there are no elements
+ * in the set or [predicate] returned `false` for every element in the set.
+ */
+ public inline fun firstOrNull(predicate: (element: E) -> Boolean): E? {
+ contract { callsInPlace(predicate) }
+ forEach { if (predicate(it)) return it }
+ return null
+ }
+
+ /**
+ * Returns the last element in the collection.
+ *
+ * @throws NoSuchElementException if the collection is empty
+ */
+ public fun last(): E {
+ forEachReverse {
+ return it
+ }
+ throw NoSuchElementException("The OrderedScatterSet is empty")
+ }
+
+ /**
+ * Returns the last element in insertion order in the collection for which [predicate] returns
+ * `true`.
+ *
+ * @param predicate called with each element until it returns `true`.
+ * @return The element for which [predicate] returns `true`.
+ * @throws NoSuchElementException if [predicate] returns `false` for all elements or the
+ * collection is empty.
+ */
+ public fun last(predicate: (element: E) -> Boolean): E {
+ contract { callsInPlace(predicate) }
+ forEachReverse { if (predicate(it)) return it }
+ throw NoSuchElementException("Could not find a match")
+ }
+
+ /**
+ * Returns the first element in insertion order in the collection for which [predicate] returns
+ * `true` or `null` if there are no elements that match [predicate].
+ *
+ * @param predicate called with each element until it returns `true`.
+ * @return The element for which [predicate] returns `true` or `null` if there are no elements
+ * in the set or [predicate] returned `false` for every element in the set.
+ */
+ public inline fun lastOrNull(predicate: (element: E) -> Boolean): E? {
+ contract { callsInPlace(predicate) }
+ forEachReverse { if (predicate(it)) return it }
+ return null
+ }
+
+ internal inline fun forEachIndex(block: (index: Int) -> Unit) {
+ contract { callsInPlace(block) }
+ val nodes = nodes
+
+ var candidate = tail
+ while (candidate != NodeInvalidLink) {
+ val previousNode = nodes[candidate].previousNode
+ @Suppress("UNCHECKED_CAST") block(candidate)
+ candidate = previousNode
+ }
+ }
+
+ /** Iterates over every element stored in this set by invoking the specified [block] lambda. */
+ @PublishedApi
+ internal inline fun unorderedForEachIndex(block: (index: Int) -> Unit) {
+ contract { callsInPlace(block) }
+ val m = metadata
+ val lastIndex = m.size - 2 // We always have 0 or at least 2 elements
+
+ for (i in 0..lastIndex) {
+ var slot = m[i]
+ if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+ // Branch-less if (i == lastIndex) 7 else 8
+ // i - lastIndex returns a negative value when i < lastIndex,
+ // so 1 is set as the MSB. By inverting and shifting we get
+ // 0 when i < lastIndex, 1 otherwise.
+ val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+ for (j in 0 until bitCount) {
+ if (isFull(slot and 0xFFL)) {
+ val index = (i shl 3) + j
+ block(index)
+ }
+ slot = slot shr 8
+ }
+ if (bitCount != 8) return
+ }
+ }
+ }
+
+ /**
+ * Iterates over every element stored in this set by invoking the specified [block] lambda. The
+ * iteration order is the same as the insertion order. It is safe to remove the element passed
+ * to [block] during iteration.
+ *
+ * @param block called with each element in the set
+ */
+ public inline fun forEach(block: (element: E) -> Unit) {
+ contract { callsInPlace(block) }
+ val elements = elements
+ val nodes = nodes
+
+ var candidate = tail
+ while (candidate != NodeInvalidLink) {
+ val previousNode = nodes[candidate].previousNode
+ @Suppress("UNCHECKED_CAST") block(elements[candidate] as E)
+ candidate = previousNode
+ }
+ }
+
+ /**
+ * Iterates over every element stored in this set by invoking the specified [block] lambda. The
+ * iteration order is the reverse of the insertion order. It is safe to remove the element
+ * passed to [block] during iteration.
+ *
+ * @param block called with each element in the set
+ */
+ public inline fun forEachReverse(block: (element: E) -> Unit) {
+ contract { callsInPlace(block) }
+ val elements = elements
+ val nodes = nodes
+
+ var candidate = head
+ while (candidate != NodeInvalidLink) {
+ val nextNode = nodes[candidate].nextNode
+ @Suppress("UNCHECKED_CAST") block(elements[candidate] as E)
+ candidate = nextNode
+ }
+ }
+
+ /**
+ * Iterates over every element stored in this set by invoking the specified [block] lambda. The
+ * elements are iterated in arbitrary order.
+ *
+ * @param block called with each element in the set
+ */
+ @PublishedApi
+ internal inline fun unorderedForEach(block: (element: E) -> Unit) {
+ contract { callsInPlace(block) }
+ val elements = elements
+ unorderedForEachIndex { index -> @Suppress("UNCHECKED_CAST") block(elements[index] as E) }
+ }
+
+ /** Returns a new list containing this set's entries, in insertion order. */
+ public inline fun toList(): List<E> {
+ return ArrayList<E>(size).apply {
+ this@OrderedScatterSet.forEach { element -> add(element) }
+ }
+ }
+
+ /**
+ * Returns true if all elements match the given [predicate]. If there are no elements in the
+ * set, `true` is returned. The order in which the predicate is called over the elements is
+ * arbitrary.
+ *
+ * @param predicate called for elements in the set to determine if it returns return `true` for
+ * all elements.
+ */
+ public inline fun all(predicate: (element: E) -> Boolean): Boolean {
+ contract { callsInPlace(predicate) }
+ unorderedForEach { element -> if (!predicate(element)) return false }
+ return true
+ }
+
+ /**
+ * Returns true if at least one element matches the given [predicate]. The order in which the
+ * predicate is called over the elements is arbitrary.
+ *
+ * @param predicate called for elements in the set to determine if it returns `true` for any
+ * elements.
+ */
+ public inline fun any(predicate: (element: E) -> Boolean): Boolean {
+ contract { callsInPlace(predicate) }
+ unorderedForEach { element -> if (predicate(element)) return true }
+ return false
+ }
+
+ /** Returns the number of elements in this set. */
+ @IntRange(from = 0) public fun count(): Int = size
+
+ /**
+ * Returns the number of elements matching the given [predicate].The order in which the
+ * predicate is called over the elements is arbitrary.
+ *
+ * @param predicate Called for all elements in the set to count the number for which it returns
+ * `true`.
+ */
+ @IntRange(from = 0)
+ public inline fun count(predicate: (element: E) -> Boolean): Int {
+ contract { callsInPlace(predicate) }
+ var count = 0
+ unorderedForEach { element -> if (predicate(element)) count++ }
+ return count
+ }
+
+ /**
+ * Returns true if the specified [element] is present in this hash set, false otherwise.
+ *
+ * @param element The element to look for in this set
+ */
+ public operator fun contains(element: E): Boolean = findElementIndex(element) >= 0
+
+ /**
+ * Creates a String from the elements separated by [separator] and using [prefix] before and
+ * [postfix] after, if supplied.
+ *
+ * When a non-negative value of [limit] is provided, a maximum of [limit] items are used to
+ * generate the string. If the collection holds more than [limit] items, the string is
+ * terminated with [truncated].
+ *
+ * [transform] may be supplied to convert each element to a custom String.
+ */
+ @JvmOverloads
+ public fun joinToString(
+ separator: CharSequence = ", ",
+ prefix: CharSequence = "",
+ postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+ limit: Int = -1,
+ truncated: CharSequence = "...",
+ transform: ((E) -> CharSequence)? = null
+ ): String = buildString {
+ append(prefix)
+ var index = 0
+ this@OrderedScatterSet.forEach { element ->
+ if (index == limit) {
+ append(truncated)
+ return@buildString
+ }
+ if (index != 0) {
+ append(separator)
+ }
+ if (transform == null) {
+ append(element)
+ } else {
+ append(transform(element))
+ }
+ index++
+ }
+ append(postfix)
+ }
+
+ /**
+ * Returns the hash code value for this set. The hash code of a set is based on the sum of the
+ * hash codes of the elements in the set, where the hash code of a null element is defined to be
+ * zero.
+ */
+ public override fun hashCode(): Int {
+ var hash = _capacity
+ hash = 31 * hash + _size
+
+ unorderedForEach { element ->
+ if (element != this) {
+ hash += element.hashCode()
+ }
+ }
+
+ return hash
+ }
+
+ /**
+ * Compares the specified object [other] with this hash set for equality. The two objects are
+ * considered equal if [other]:
+ * - Is a [OrderedScatterSet]
+ * - Has the same [size] as this set
+ * - Contains elements equal to this set's elements
+ */
+ public override fun equals(other: Any?): Boolean {
+ if (other === this) {
+ return true
+ }
+
+ if (other !is OrderedScatterSet<*>) {
+ return false
+ }
+ if (other.size != size) {
+ return false
+ }
+
+ @Suppress("UNCHECKED_CAST") val o = other as OrderedScatterSet<Any?>
+
+ unorderedForEach { element ->
+ if (element !in o) {
+ return false
+ }
+ }
+
+ return true
+ }
+
+ /**
+ * Returns a string representation of this set. The set is denoted in the string by the `[]`.
+ * Each element is separated by `, `.
+ */
+ override fun toString(): String =
+ joinToString(prefix = "[", postfix = "]") { element ->
+ if (element === this) {
+ "(this)"
+ } else {
+ element.toString()
+ }
+ }
+
+ /**
+ * Scans the set to find the index in the backing arrays of the specified [element]. Returns -1
+ * if the element is not present.
+ */
+ internal inline fun findElementIndex(element: E): Int {
+ val hash = hash(element)
+ val hash2 = h2(hash)
+
+ val probeMask = _capacity
+ var probeOffset = h1(hash) and probeMask
+ var probeIndex = 0
+ while (true) {
+ val g = group(metadata, probeOffset)
+ var m = g.match(hash2)
+ while (m.hasNext()) {
+ val index = (probeOffset + m.get()) and probeMask
+ if (elements[index] == element) {
+ return index
+ }
+ m = m.next()
+ }
+
+ if (g.maskEmpty() != 0L) {
+ break
+ }
+
+ probeIndex += GroupWidth
+ probeOffset = (probeOffset + probeIndex) and probeMask
+ }
+
+ return -1
+ }
+
+ /**
+ * Wraps this [OrderedScatterSet] with a [Set] interface. The [Set] is backed by the
+ * [OrderedScatterSet], so changes to the [OrderedScatterSet] are reflected in the [Set]. If the
+ * [OrderedScatterSet] is modified while an iteration over the [Set] is in progress, the results
+ * of the iteration are undefined.
+ *
+ * **Note**: while this method is useful to use this [OrderedScatterSet] with APIs accepting
+ * [Set] interfaces, it is less efficient to do so than to use [OrderedScatterSet]'s APIs
+ * directly. While the [Set] implementation returned by this method tries to be as efficient as
+ * possible, the semantics of [Set] may require the allocation of temporary objects for access
+ * and iteration.
+ */
+ public fun asSet(): Set<E> = SetWrapper()
+
+ internal open inner class SetWrapper : Set<E> {
+ override val size: Int
+ get() = this@OrderedScatterSet._size
+
+ override fun containsAll(elements: Collection<E>): Boolean {
+ elements.forEach { element ->
+ if (!this@OrderedScatterSet.contains(element)) {
+ return false
+ }
+ }
+ return true
+ }
+
+ @Suppress("KotlinOperator")
+ override fun contains(element: E): Boolean {
+ return this@OrderedScatterSet.contains(element)
+ }
+
+ override fun isEmpty(): Boolean = this@OrderedScatterSet.isEmpty()
+
+ override fun iterator(): Iterator<E> {
+ return iterator { this@OrderedScatterSet.forEach { element -> yield(element) } }
+ }
+ }
+}
+
+/**
+ * [MutableOrderedScatterSet] is a container with a [MutableSet]-like interface based on a flat hash
+ * table implementation that preserves the insertion order for iteration. The underlying
+ * implementation is designed to avoid all allocations on insertion, removal, retrieval, and
+ * iteration. Allocations may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added elements to the set.
+ *
+ * This implementation guarantees the order of the elements when iterating over them using [forEach]
+ * or the iterator provided by [asMutableSet].
+ *
+ * This implementation is not thread-safe: if multiple threads access this container concurrently,
+ * and one or more threads modify the structure of the set (insertion or removal for instance), the
+ * calling code must provide the appropriate synchronization. Concurrent reads are however safe.
+ *
+ * **Note**: when a [Set] is absolutely necessary, you can use the method [asSet] to create a thin
+ * wrapper around a [MutableOrderedScatterSet]. Please refer to [asSet] for more details and
+ * caveats.
+ *
+ * **Note**: when a [MutableSet] is absolutely necessary, you can use the method [asMutableSet] to
+ * create a thin wrapper around a [MutableOrderedScatterSet]. Please refer to [asMutableSet] for
+ * more details and caveats.
+ *
+ * @param initialCapacity The initial desired capacity for this container. the container will honor
+ * this value by guaranteeing its internal structures can hold that many elements without
+ * requiring any allocations. The initial capacity can be set to 0.
+ * @constructor Creates a new [MutableOrderedScatterSet]
+ * @see Set
+ */
+public class MutableOrderedScatterSet<E>(initialCapacity: Int = DefaultScatterCapacity) :
+ OrderedScatterSet<E>() {
+ // Number of elements we can add before we need to grow
+ private var growthLimit = 0
+
+ init {
+ requirePrecondition(initialCapacity >= 0) { "Capacity must be a positive value." }
+ initializeStorage(unloadedCapacity(initialCapacity))
+ }
+
+ private fun initializeStorage(initialCapacity: Int) {
+ val newCapacity =
+ if (initialCapacity > 0) {
+ // Since we use longs for storage, our capacity is never < 7, enforce
+ // it here. We do have a special case for 0 to create small empty maps
+ maxOf(7, normalizeCapacity(initialCapacity))
+ } else {
+ 0
+ }
+ _capacity = newCapacity
+ initializeMetadata(newCapacity)
+ elements = if (newCapacity == 0) EMPTY_OBJECTS else arrayOfNulls(newCapacity)
+ nodes =
+ if (newCapacity == 0) EmptyNodes else LongArray(newCapacity).apply { fill(EmptyNode) }
+ }
+
+ private fun initializeMetadata(capacity: Int) {
+ metadata =
+ if (capacity == 0) {
+ EmptyGroup
+ } else {
+ // Round up to the next multiple of 8 and find how many longs we need
+ val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+ LongArray(size).apply { fill(AllEmpty) }
+ }
+ writeRawMetadata(metadata, capacity, Sentinel)
+ initializeGrowth()
+ }
+
+ private fun initializeGrowth() {
+ growthLimit = loadedCapacity(capacity) - _size
+ }
+
+ /**
+ * Adds the specified element to the set.
+ *
+ * @param element The element to add to the set.
+ * @return `true` if the element has been added or `false` if the element is already contained
+ * within the set.
+ */
+ public fun add(element: E): Boolean {
+ val oldSize = size
+ val index = findAbsoluteInsertIndex(element)
+ elements[index] = element
+ moveNodeToHead(index)
+ return size != oldSize
+ }
+
+ /**
+ * Adds the specified element to the set.
+ *
+ * @param element The element to add to the set.
+ */
+ public operator fun plusAssign(element: E) {
+ val index = findAbsoluteInsertIndex(element)
+ elements[index] = element
+ moveNodeToHead(index)
+ }
+
+ /**
+ * Adds all the [elements] into this set.
+ *
+ * @param elements An array of elements to add to the set.
+ * @return `true` if any of the specified elements were added to the collection, `false` if the
+ * collection was not modified.
+ */
+ public fun addAll(@Suppress("ArrayReturn") elements: Array<out E>): Boolean {
+ val oldSize = size
+ plusAssign(elements)
+ return oldSize != size
+ }
+
+ /**
+ * Adds all the [elements] into this set.
+ *
+ * @param elements Iterable elements to add to the set.
+ * @return `true` if any of the specified elements were added to the collection, `false` if the
+ * collection was not modified.
+ */
+ public fun addAll(elements: Iterable<E>): Boolean {
+ val oldSize = size
+ plusAssign(elements)
+ return oldSize != size
+ }
+
+ /**
+ * Adds all the [elements] into this set.
+ *
+ * @param elements The sequence of elements to add to the set.
+ * @return `true` if any of the specified elements were added to the collection, `false` if the
+ * collection was not modified.
+ */
+ public fun addAll(elements: Sequence<E>): Boolean {
+ val oldSize = size
+ plusAssign(elements)
+ return oldSize != size
+ }
+
+ /**
+ * Adds all the elements in the [elements] set into this set.
+ *
+ * @param elements A [OrderedScatterSet] whose elements are to be added to the set
+ * @return `true` if any of the specified elements were added to the collection, `false` if the
+ * collection was not modified.
+ */
+ public fun addAll(elements: OrderedScatterSet<E>): Boolean {
+ val oldSize = size
+ plusAssign(elements)
+ return oldSize != size
+ }
+
+ /**
+ * Adds all the elements in the [elements] set into this set.
+ *
+ * @param elements A [ScatterSet] whose elements are to be added to the set
+ * @return `true` if any of the specified elements were added to the collection, `false` if the
+ * collection was not modified.
+ */
+ public fun addAll(elements: ScatterSet<E>): Boolean {
+ val oldSize = size
+ plusAssign(elements)
+ return oldSize != size
+ }
+
+ /**
+ * Adds all the elements in the [elements] set into this set.
+ *
+ * @param elements An [ObjectList] whose elements are to be added to the set
+ * @return `true` if any of the specified elements were added to the collection, `false` if the
+ * collection was not modified.
+ */
+ public fun addAll(elements: ObjectList<E>): Boolean {
+ val oldSize = size
+ plusAssign(elements)
+ return oldSize != size
+ }
+
+ /**
+ * Adds all the [elements] into this set.
+ *
+ * @param elements An array of elements to add to the set.
+ */
+ public operator fun plusAssign(@Suppress("ArrayReturn") elements: Array<out E>) {
+ elements.forEach { element -> plusAssign(element) }
+ }
+
+ /**
+ * Adds all the [elements] into this set.
+ *
+ * @param elements Iterable elements to add to the set.
+ */
+ public operator fun plusAssign(elements: Iterable<E>) {
+ elements.forEach { element -> plusAssign(element) }
+ }
+
+ /**
+ * Adds all the [elements] into this set.
+ *
+ * @param elements The sequence of elements to add to the set.
+ */
+ public operator fun plusAssign(elements: Sequence<E>) {
+ elements.forEach { element -> plusAssign(element) }
+ }
+
+ /**
+ * Adds all the elements in the [elements] set into this set.
+ *
+ * @param elements A [OrderedScatterSet] whose elements are to be added to the set
+ */
+ public operator fun plusAssign(elements: OrderedScatterSet<E>) {
+ elements.forEach { element -> plusAssign(element) }
+ }
+
+ /**
+ * Adds all the elements in the [elements] set into this set.
+ *
+ * @param elements A [ScatterSet] whose elements are to be added to the set
+ */
+ public operator fun plusAssign(elements: ScatterSet<E>) {
+ elements.forEach { element -> plusAssign(element) }
+ }
+
+ /**
+ * Adds all the elements in the [elements] set into this set.
+ *
+ * @param elements An [ObjectList] whose elements are to be added to the set
+ */
+ public operator fun plusAssign(elements: ObjectList<E>) {
+ elements.forEach { element -> plusAssign(element) }
+ }
+
+ /**
+ * Removes the specified [element] from the set.
+ *
+ * @param element The element to be removed from the set.
+ * @return `true` if the [element] was present in the set, or `false` if it wasn't present
+ * before removal.
+ */
+ public fun remove(element: E): Boolean {
+ val index = findElementIndex(element)
+ val exists = index >= 0
+ if (exists) {
+ removeElementAt(index)
+ }
+ return exists
+ }
+
+ /**
+ * Removes the specified [element] from the set if it is present.
+ *
+ * @param element The element to be removed from the set.
+ */
+ public operator fun minusAssign(element: E) {
+ val index = findElementIndex(element)
+ if (index >= 0) {
+ removeElementAt(index)
+ }
+ }
+
+ /**
+ * Removes the specified [elements] from the set, if present.
+ *
+ * @param elements An array of elements to be removed from the set.
+ * @return `true` if the set was changed or `false` if none of the elements were present.
+ */
+ public fun removeAll(@Suppress("ArrayReturn") elements: Array<out E>): Boolean {
+ val oldSize = size
+ minusAssign(elements)
+ return oldSize != size
+ }
+
+ /**
+ * Removes the specified [elements] from the set, if present.
+ *
+ * @param elements A sequence of elements to be removed from the set.
+ * @return `true` if the set was changed or `false` if none of the elements were present.
+ */
+ public fun removeAll(elements: Sequence<E>): Boolean {
+ val oldSize = size
+ minusAssign(elements)
+ return oldSize != size
+ }
+
+ /**
+ * Removes the specified [elements] from the set, if present.
+ *
+ * @param elements A Iterable of elements to be removed from the set.
+ * @return `true` if the set was changed or `false` if none of the elements were present.
+ */
+ public fun removeAll(elements: Iterable<E>): Boolean {
+ val oldSize = size
+ minusAssign(elements)
+ return oldSize != size
+ }
+
+ /**
+ * Removes the specified [elements] from the set, if present.
+ *
+ * @param elements A [OrderedScatterSet] whose elements should be removed from the set.
+ * @return `true` if the set was changed or `false` if none of the elements were present.
+ */
+ public fun removeAll(elements: OrderedScatterSet<E>): Boolean {
+ val oldSize = size
+ minusAssign(elements)
+ return oldSize != size
+ }
+
+ /**
+ * Removes the specified [elements] from the set, if present.
+ *
+ * @param elements A [ScatterSet] whose elements should be removed from the set.
+ * @return `true` if the set was changed or `false` if none of the elements were present.
+ */
+ public fun removeAll(elements: ScatterSet<E>): Boolean {
+ val oldSize = size
+ minusAssign(elements)
+ return oldSize != size
+ }
+
+ /**
+ * Removes the specified [elements] from the set, if present.
+ *
+ * @param elements An [ObjectList] whose elements should be removed from the set.
+ * @return `true` if the set was changed or `false` if none of the elements were present.
+ */
+ public fun removeAll(elements: ObjectList<E>): Boolean {
+ val oldSize = size
+ minusAssign(elements)
+ return oldSize != size
+ }
+
+ /**
+ * Removes the specified [elements] from the set, if present.
+ *
+ * @param elements An array of elements to be removed from the set.
+ */
+ public operator fun minusAssign(@Suppress("ArrayReturn") elements: Array<out E>) {
+ elements.forEach { element -> minusAssign(element) }
+ }
+
+ /**
+ * Removes the specified [elements] from the set, if present.
+ *
+ * @param elements A sequence of elements to be removed from the set.
+ */
+ public operator fun minusAssign(elements: Sequence<E>) {
+ elements.forEach { element -> minusAssign(element) }
+ }
+
+ /**
+ * Removes the specified [elements] from the set, if present.
+ *
+ * @param elements A Iterable of elements to be removed from the set.
+ */
+ public operator fun minusAssign(elements: Iterable<E>) {
+ elements.forEach { element -> minusAssign(element) }
+ }
+
+ /**
+ * Removes the specified [elements] from the set, if present.
+ *
+ * @param elements A [OrderedScatterSet] whose elements should be removed from the set.
+ */
+ public operator fun minusAssign(elements: OrderedScatterSet<E>) {
+ elements.forEach { element -> minusAssign(element) }
+ }
+
+ /**
+ * Removes the specified [elements] from the set, if present.
+ *
+ * @param elements A [ScatterSet] whose elements should be removed from the set.
+ */
+ public operator fun minusAssign(elements: ScatterSet<E>) {
+ elements.forEach { element -> minusAssign(element) }
+ }
+
+ /**
+ * Removes the specified [elements] from the set, if present.
+ *
+ * @param elements An [ObjectList] whose elements should be removed from the set.
+ */
+ public operator fun minusAssign(elements: ObjectList<E>) {
+ elements.forEach { element -> minusAssign(element) }
+ }
+
+ /** Removes any values for which the specified [predicate] returns true. */
+ public inline fun removeIf(predicate: (E) -> Boolean) {
+ val elements = elements
+ unorderedForEachIndex { index ->
+ @Suppress("UNCHECKED_CAST")
+ if (predicate(elements[index] as E)) {
+ removeElementAt(index)
+ }
+ }
+ }
+
+ /**
+ * Removes all the entries in this set that are not contained in [elements].
+ *
+ * @param elements A collection of elements to preserve in this set.
+ * @return `true` if this set was modified, `false` otherwise.
+ */
+ public fun retainAll(elements: Collection<E>): Boolean {
+ val internalElements = this.elements
+ val startSize = _size
+ unorderedForEachIndex { index ->
+ if (internalElements[index] !in elements) {
+ removeElementAt(index)
+ }
+ }
+ return startSize != _size
+ }
+
+ /**
+ * Removes all the entries in this set that are not contained in [elements].
+ *
+ * @params elements A set of elements to preserve in this set.
+ * @return `true` if this set was modified, `false` otherwise.
+ */
+ public fun retainAll(elements: OrderedScatterSet<E>): Boolean {
+ val internalElements = this.elements
+ val startSize = _size
+ unorderedForEachIndex { index ->
+ @Suppress("UNCHECKED_CAST")
+ if (internalElements[index] as E !in elements) {
+ removeElementAt(index)
+ }
+ }
+ return startSize != _size
+ }
+
+ /**
+ * Removes all the entries in this set that are not contained in [elements].
+ *
+ * @params elements A set of elements to preserve in this set.
+ * @return `true` if this set was modified, `false` otherwise.
+ */
+ public fun retainAll(elements: ScatterSet<E>): Boolean {
+ val internalElements = this.elements
+ val startSize = _size
+ unorderedForEachIndex { index ->
+ @Suppress("UNCHECKED_CAST")
+ if (internalElements[index] as E !in elements) {
+ removeElementAt(index)
+ }
+ }
+ return startSize != _size
+ }
+
+ /**
+ * Removes all the elements in this set for which the specified [predicate] is `true`. For each
+ * element in the set, the predicate is invoked with that element as the sole parameter.
+ *
+ * @param predicate Predicate invoked for each element in the set. When the predicate returns
+ * `true`, the element is kept in the set, otherwise it is removed.
+ * @return `true` if this set was modified, `false` otherwise.
+ */
+ public fun retainAll(predicate: (E) -> Boolean): Boolean {
+ val elements = elements
+ val startSize = _size
+ unorderedForEachIndex { index ->
+ @Suppress("UNCHECKED_CAST")
+ if (!predicate(elements[index] as E)) {
+ removeElementAt(index)
+ }
+ }
+ return startSize != _size
+ }
+
+ @PublishedApi
+ internal fun removeElementAt(index: Int) {
+ _size -= 1
+
+ // TODO: We could just mark the element as empty if there's a group
+ // window around this element that was already empty
+ writeMetadata(metadata, _capacity, index, Deleted)
+ elements[index] = null
+
+ removeNode(index)
+ }
+
+ private inline fun moveNodeToHead(index: Int) {
+ nodes[index] = createLinkToNext(head)
+
+ if (head != NodeInvalidLink) {
+ nodes[head] = setLinkToPrevious(nodes[head], index)
+ }
+ head = index
+
+ if (tail == NodeInvalidLink) {
+ tail = index
+ }
+ }
+
+ private inline fun removeNode(index: Int) {
+ val nodes = nodes
+ val node = nodes[index]
+ val previousIndex = node.previousNode
+ val nextIndex = node.nextNode
+
+ if (previousIndex != NodeInvalidLink) {
+ nodes[previousIndex] = setLinkToNext(nodes[previousIndex], nextIndex)
+ } else {
+ head = nextIndex
+ }
+
+ if (nextIndex != NodeInvalidLink) {
+ nodes[nextIndex] = setLinkToPrevious(nodes[nextIndex], previousIndex)
+ } else {
+ tail = previousIndex
+ }
+
+ nodes[index] = EmptyNode
+ }
+
+ /** Removes all elements from this set. */
+ public fun clear() {
+ _size = 0
+ if (metadata !== EmptyGroup) {
+ metadata.fill(AllEmpty)
+ writeRawMetadata(metadata, _capacity, Sentinel)
+ }
+ elements.fill(null, 0, _capacity)
+ nodes.fill(EmptyNode)
+ head = NodeInvalidLink
+ tail = NodeInvalidLink
+ initializeGrowth()
+ }
+
+ /**
+ * Scans the set to find the index at which we can store the given [element]. If the element
+ * already exists in the set, its index will be returned, otherwise the index of an empty slot
+ * will be returned. Calling this function may cause the internal storage to be reallocated if
+ * the set is full.
+ */
+ private fun findAbsoluteInsertIndex(element: E): Int {
+ val hash = hash(element)
+ val hash1 = h1(hash)
+ val hash2 = h2(hash)
+
+ val probeMask = _capacity
+ var probeOffset = hash1 and probeMask
+ var probeIndex = 0
+
+ while (true) {
+ val g = group(metadata, probeOffset)
+ var m = g.match(hash2)
+ while (m.hasNext()) {
+ val index = (probeOffset + m.get()) and probeMask
+ if (elements[index] == element) {
+ return index
+ }
+ m = m.next()
+ }
+
+ if (g.maskEmpty() != 0L) {
+ break
+ }
+
+ probeIndex += GroupWidth
+ probeOffset = (probeOffset + probeIndex) and probeMask
+ }
+
+ var index = findFirstAvailableSlot(hash1)
+ if (growthLimit == 0 && !isDeleted(metadata, index)) {
+ adjustStorage()
+ index = findFirstAvailableSlot(hash1)
+ }
+
+ _size += 1
+ growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+ writeMetadata(metadata, _capacity, index, hash2.toLong())
+
+ return index
+ }
+
+ /**
+ * Finds the first empty or deleted slot in the set in which we can store an element without
+ * resizing the internal storage.
+ */
+ private fun findFirstAvailableSlot(hash1: Int): Int {
+ val probeMask = _capacity
+ var probeOffset = hash1 and probeMask
+ var probeIndex = 0
+ while (true) {
+ val g = group(metadata, probeOffset)
+ val m = g.maskEmptyOrDeleted()
+ if (m != 0L) {
+ return (probeOffset + m.lowestBitSet()) and probeMask
+ }
+ probeIndex += GroupWidth
+ probeOffset = (probeOffset + probeIndex) and probeMask
+ }
+ }
+
+ /**
+ * Trims this [MutableOrderedScatterSet]'s storage so it is sized appropriately to hold the
+ * current elements.
+ *
+ * Returns the number of empty elements removed from this set's storage. Returns 0 if no
+ * trimming is necessary or possible.
+ */
+ @IntRange(from = 0)
+ public fun trim(): Int {
+ val previousCapacity = _capacity
+ val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+ if (newCapacity < previousCapacity) {
+ resizeStorage(newCapacity)
+ return previousCapacity - _capacity
+ }
+ return 0
+ }
+
+ /**
+ * Remove entries until the total size of the remaining entries is less than or equal to
+ * [maxSize]. Entries added last are removed first. Calling this method has no effect if
+ * [maxSize] is greater than [size].
+ */
+ public fun trimToSize(maxSize: Int) {
+ val nodes = nodes
+ var candidate = head
+ while (candidate != NodeInvalidLink && _size > maxSize && _size != 0) {
+ val nextNode = nodes[candidate].nextNode
+ removeElementAt(candidate)
+ candidate = nextNode
+ }
+ }
+
+ /**
+ * Grow internal storage if necessary. This function can instead opt to remove deleted elements
+ * from the set to avoid an expensive reallocation of the underlying storage. This "rehash in
+ * place" occurs when the current size is <= 25/32 of the set capacity. The choice of 25/32 is
+ * detailed in the implementation of abseil's `raw_hash_map`.
+ */
+ internal fun adjustStorage() { // Internal to prevent inlining
+ if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+ dropDeletes()
+ } else {
+ resizeStorage(nextCapacity(_capacity))
+ }
+ }
+
+ // Internal to prevent inlining
+ internal fun dropDeletes() {
+ val metadata = metadata
+ // TODO: This shouldn't be required, but without it the compiler generates an extra
+ // 200+ aarch64 instructions to generate a NullPointerException.
+ @Suppress("SENSELESS_COMPARISON") if (metadata == null) return
+
+ val capacity = _capacity
+ val elements = elements
+ val nodes = nodes
+
+ val indexMapping = IntArray(capacity)
+
+ // Converts Sentinel and Deleted to Empty, and Full to Deleted
+ convertMetadataForCleanup(metadata, capacity)
+
+ var swapIndex = -1
+ var index = 0
+
+ // Drop deleted items and re-hashes surviving entries
+ while (index != capacity) {
+ var m = readRawMetadata(metadata, index)
+ // Formerly Deleted entry, we can use it as a swap spot
+ if (m == Empty) {
+ swapIndex = index
+ index++
+ continue
+ }
+
+ // Formerly Full entries are now marked Deleted. If we see an
+ // entry that's not marked Deleted, we can ignore it completely
+ if (m != Deleted) {
+ index++
+ continue
+ }
+
+ val hash = hash(elements[index])
+ val hash1 = h1(hash)
+ val targetIndex = findFirstAvailableSlot(hash1)
+
+ // Test if the current index (i) and the new index (targetIndex) fall
+ // within the same group based on the hash. If the group doesn't change,
+ // we don't move the entry
+ val probeOffset = hash1 and capacity
+ val newProbeIndex = ((targetIndex - probeOffset) and capacity) / GroupWidth
+ val oldProbeIndex = ((index - probeOffset) and capacity) / GroupWidth
+
+ if (newProbeIndex == oldProbeIndex) {
+ val hash2 = h2(hash)
+ writeRawMetadata(metadata, index, hash2.toLong())
+
+ indexMapping[index] = index
+
+ // Copies the metadata into the clone area
+ metadata[metadata.size - 1] = metadata[0]
+
+ index++
+ continue
+ }
+
+ m = readRawMetadata(metadata, targetIndex)
+ if (m == Empty) {
+ // The target is empty so we can transfer directly
+ val hash2 = h2(hash)
+ writeRawMetadata(metadata, targetIndex, hash2.toLong())
+ writeRawMetadata(metadata, index, Empty)
+
+ elements[targetIndex] = elements[index]
+ elements[index] = null
+
+ nodes[targetIndex] = nodes[index]
+ nodes[index] = EmptyNode
+
+ indexMapping[index] = targetIndex
+
+ swapIndex = index
+ } else /* m == Deleted */ {
+ // The target isn't empty so we use an empty slot denoted by
+ // swapIndex to perform the swap
+ val hash2 = h2(hash)
+ writeRawMetadata(metadata, targetIndex, hash2.toLong())
+
+ if (swapIndex == -1) {
+ swapIndex = findEmptySlot(metadata, index + 1, capacity)
+ }
+
+ elements[swapIndex] = elements[targetIndex]
+ elements[targetIndex] = elements[index]
+ elements[index] = elements[swapIndex]
+
+ nodes[swapIndex] = nodes[targetIndex]
+ nodes[targetIndex] = nodes[index]
+ nodes[index] = nodes[swapIndex]
+
+ indexMapping[index] = targetIndex
+ indexMapping[targetIndex] = index
+
+ // Since we exchanged two slots we must repeat the process with
+ // element we just moved in the current location
+ index--
+ }
+
+ // Copies the metadata into the clone area
+ metadata[metadata.size - 1] = metadata[0]
+
+ index++
+ }
+
+ initializeGrowth()
+
+ fixupNodes(indexMapping)
+ }
+
+ // Internal to prevent inlining
+ internal fun resizeStorage(newCapacity: Int) {
+ val previousMetadata = metadata
+ val previousElements = elements
+ val previousNodes = nodes
+ val previousCapacity = _capacity
+
+ val indexMapping = IntArray(previousCapacity)
+
+ initializeStorage(newCapacity)
+
+ val newMetadata = metadata
+ val newElements = elements
+ val newNodes = nodes
+ val capacity = _capacity
+
+ for (i in 0 until previousCapacity) {
+ if (isFull(previousMetadata, i)) {
+ val previousKey = previousElements[i]
+ val hash = hash(previousKey)
+ val index = findFirstAvailableSlot(h1(hash))
+
+ writeMetadata(newMetadata, capacity, index, h2(hash).toLong())
+ newElements[index] = previousKey
+ newNodes[index] = previousNodes[i]
+
+ indexMapping[i] = index
+ }
+ }
+
+ fixupNodes(indexMapping)
+ }
+
+ private fun fixupNodes(mapping: IntArray) {
+ val nodes = nodes
+ for (i in nodes.indices) {
+ val node = nodes[i]
+ val previous = node.previousNode
+ val next = node.nextNode
+ nodes[i] = createLinks(node, previous, next, mapping)
+ }
+ if (head != NodeInvalidLink) head = mapping[head]
+ if (tail != NodeInvalidLink) tail = mapping[tail]
+ }
+
+ /**
+ * Wraps this [OrderedScatterSet] with a [MutableSet] interface. The [MutableSet] is backed by
+ * the [OrderedScatterSet], so changes to the [OrderedScatterSet] are reflected in the
+ * [MutableSet] and vice-versa. If the [OrderedScatterSet] is modified while an iteration over
+ * the [MutableSet] is in progress (and vice- versa), the results of the iteration are
+ * undefined.
+ *
+ * **Note**: while this method is useful to use this [MutableOrderedScatterSet] with APIs
+ * accepting [MutableSet] interfaces, it is less efficient to do so than to use
+ * [MutableOrderedScatterSet]'s APIs directly. While the [MutableSet] implementation returned by
+ * this method tries to be as efficient as possible, the semantics of [MutableSet] may require
+ * the allocation of temporary objects for access and iteration.
+ */
+ public fun asMutableSet(): MutableSet<E> = MutableSetWrapper()
+
+ private inner class MutableSetWrapper : SetWrapper(), MutableSet<E> {
+ override fun add(element: E): Boolean = this@MutableOrderedScatterSet.add(element)
+
+ override fun addAll(elements: Collection<E>): Boolean =
+ this@MutableOrderedScatterSet.addAll(elements)
+
+ override fun clear() {
+ this@MutableOrderedScatterSet.clear()
+ }
+
+ override fun iterator(): MutableIterator<E> =
+ object : MutableIterator<E> {
+ var current = -1
+ val iterator = iterator {
+ this@MutableOrderedScatterSet.forEachIndex { index ->
+ current = index
+ @Suppress("UNCHECKED_CAST") yield(elements[index] as E)
+ }
+ }
+
+ override fun hasNext(): Boolean = iterator.hasNext()
+
+ override fun next(): E = iterator.next()
+
+ override fun remove() {
+ if (current != -1) {
+ this@MutableOrderedScatterSet.removeElementAt(current)
+ current = -1
+ }
+ }
+ }
+
+ override fun remove(element: E): Boolean = this@MutableOrderedScatterSet.remove(element)
+
+ override fun retainAll(elements: Collection<E>): Boolean =
+ this@MutableOrderedScatterSet.retainAll(elements)
+
+ override fun removeAll(elements: Collection<E>): Boolean =
+ this@MutableOrderedScatterSet.removeAll(elements)
+ }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt
index c3eb566..816650c 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt
@@ -98,7 +98,8 @@
* storage needs to grow to accommodate newly added elements to the set.
*
* This implementation makes no guarantee as to the order of the elements, nor does it make
- * guarantees that the order remains constant over time.
+ * guarantees that the order remains constant over time. If the order of the elements must be
+ * preserved, please refer to [OrderedScatterSet].
*
* Though [ScatterSet] offers a read-only interface, it is always backed by a [MutableScatterSet].
* Read operations alone are thread-safe. However, any mutations done through the backing
@@ -220,15 +221,15 @@
}
/**
- * Iterates over every element stored in this set by invoking the specified [block] lambda.
+ * Iterates over every element stored in this set by invoking the specified [block] lambda. It
+ * is safe to remove the element passed to [block] during iteration.
*
* @param block called with each element in the set
*/
public inline fun forEach(block: (element: E) -> Unit) {
contract { callsInPlace(block) }
- val k = elements
-
- forEachIndex { index -> @Suppress("UNCHECKED_CAST") block(k[index] as E) }
+ val elements = elements
+ forEachIndex { index -> @Suppress("UNCHECKED_CAST") block(elements[index] as E) }
}
/**
@@ -320,14 +321,19 @@
}
/**
- * Returns the hash code value for this set. The hash code of a set is defined to be the sum of
- * the hash codes of the elements in the set, where the hash code of a null element is defined
- * to be zero
+ * Returns the hash code value for this set. The hash code of a set is based on the sum of the
+ * hash codes of the elements in the set, where the hash code of a null element is defined to be
+ * zero.
*/
public override fun hashCode(): Int {
- var hash = 0
+ var hash = _capacity
+ hash = 31 * hash + _size
- forEach { element -> hash += element.hashCode() }
+ forEach { element ->
+ if (element != this) {
+ hash += element.hashCode()
+ }
+ }
return hash
}
@@ -492,7 +498,7 @@
}
_capacity = newCapacity
initializeMetadata(newCapacity)
- elements = arrayOfNulls(newCapacity)
+ elements = if (newCapacity == 0) EMPTY_OBJECTS else arrayOfNulls(newCapacity)
}
private fun initializeMetadata(capacity: Int) {
@@ -591,6 +597,19 @@
/**
* Adds all the elements in the [elements] set into this set.
*
+ * @param elements A [OrderedScatterSet] whose elements are to be added to the set
+ * @return `true` if any of the specified elements were added to the collection, `false` if the
+ * collection was not modified.
+ */
+ public fun addAll(elements: OrderedScatterSet<E>): Boolean {
+ val oldSize = size
+ plusAssign(elements)
+ return oldSize != size
+ }
+
+ /**
+ * Adds all the elements in the [elements] set into this set.
+ *
* @param elements An [ObjectList] whose elements are to be added to the set
* @return `true` if any of the specified elements were added to the collection, `false` if the
* collection was not modified.
@@ -640,6 +659,15 @@
/**
* Adds all the elements in the [elements] set into this set.
*
+ * @param elements A [OrderedScatterSet] whose elements are to be added to the set
+ */
+ public operator fun plusAssign(elements: OrderedScatterSet<E>) {
+ elements.forEach { element -> plusAssign(element) }
+ }
+
+ /**
+ * Adds all the elements in the [elements] set into this set.
+ *
* @param elements An [ObjectList] whose elements are to be added to the set
*/
public operator fun plusAssign(elements: ObjectList<E>) {
@@ -725,6 +753,18 @@
/**
* Removes the specified [elements] from the set, if present.
*
+ * @param elements A [OrderedScatterSet] whose elements should be removed from the set.
+ * @return `true` if the set was changed or `false` if none of the elements were present.
+ */
+ public fun removeAll(elements: OrderedScatterSet<E>): Boolean {
+ val oldSize = size
+ minusAssign(elements)
+ return oldSize != size
+ }
+
+ /**
+ * Removes the specified [elements] from the set, if present.
+ *
* @param elements An [ObjectList] whose elements should be removed from the set.
* @return `true` if the set was changed or `false` if none of the elements were present.
*/
@@ -773,6 +813,15 @@
/**
* Removes the specified [elements] from the set, if present.
*
+ * @param elements A [OrderedScatterSet] whose elements should be removed from the set.
+ */
+ public operator fun minusAssign(elements: OrderedScatterSet<E>) {
+ elements.forEach { element -> minusAssign(element) }
+ }
+
+ /**
+ * Removes the specified [elements] from the set, if present.
+ *
* @param elements An [ObjectList] whose elements should be removed from the set.
*/
public operator fun minusAssign(elements: ObjectList<E>) {
@@ -790,6 +839,79 @@
}
}
+ /**
+ * Removes all the entries in this set that are not contained in [elements].
+ *
+ * @param elements A collection of elements to preserve in this set.
+ * @return `true` if this set was modified, `false` otherwise.
+ */
+ public fun retainAll(elements: Collection<E>): Boolean {
+ val internalElements = this.elements
+ val startSize = _size
+ forEachIndex { index ->
+ if (internalElements[index] !in elements) {
+ removeElementAt(index)
+ }
+ }
+ return startSize != _size
+ }
+
+ /**
+ * Removes all the entries in this set that are not contained in [elements].
+ *
+ * @params elements A set of elements to preserve in this set.
+ * @return `true` if this set was modified, `false` otherwise.
+ */
+ public fun retainAll(elements: ScatterSet<E>): Boolean {
+ val internalElements = this.elements
+ val startSize = _size
+ forEachIndex { index ->
+ @Suppress("UNCHECKED_CAST")
+ if (internalElements[index] as E !in elements) {
+ removeElementAt(index)
+ }
+ }
+ return startSize != _size
+ }
+
+ /**
+ * Removes all the entries in this set that are not contained in [elements].
+ *
+ * @params elements A set of elements to preserve in this set.
+ * @return `true` if this set was modified, `false` otherwise.
+ */
+ public fun retainAll(elements: OrderedScatterSet<E>): Boolean {
+ val internalElements = this.elements
+ val startSize = _size
+ forEachIndex { index ->
+ @Suppress("UNCHECKED_CAST")
+ if (internalElements[index] as E !in elements) {
+ removeElementAt(index)
+ }
+ }
+ return startSize != _size
+ }
+
+ /**
+ * Removes all the elements in this set for which the specified [predicate] is `true`. For each
+ * element in the set, the predicate is invoked with that element as the sole parameter.
+ *
+ * @param predicate Predicate invoked for each element in the set. When the predicate returns
+ * `true`, the element is kept in the set, otherwise it is removed.
+ * @return `true` if this set was modified, `false` otherwise.
+ */
+ public fun retainAll(predicate: (E) -> Boolean): Boolean {
+ val elements = elements
+ val startSize = _size
+ forEachIndex { index ->
+ @Suppress("UNCHECKED_CAST")
+ if (!predicate(elements[index] as E)) {
+ removeElementAt(index)
+ }
+ }
+ return startSize != _size
+ }
+
@PublishedApi
internal fun removeElementAt(index: Int) {
_size -= 1
@@ -1072,25 +1194,10 @@
override fun remove(element: E): Boolean = this@MutableScatterSet.remove(element)
- override fun retainAll(elements: Collection<E>): Boolean {
- var changed = false
- this@MutableScatterSet.forEachIndex { index ->
- @Suppress("UNCHECKED_CAST")
- val element = this@MutableScatterSet.elements[index] as E
- if (element !in elements) {
- this@MutableScatterSet.removeElementAt(index)
- changed = true
- }
- }
- return changed
- }
+ override fun retainAll(elements: Collection<E>): Boolean =
+ this@MutableScatterSet.retainAll(elements)
- override fun removeAll(elements: Collection<E>): Boolean {
- val oldSize = this@MutableScatterSet.size
- for (element in elements) {
- this@MutableScatterSet -= element
- }
- return oldSize != this@MutableScatterSet.size
- }
+ override fun removeAll(elements: Collection<E>): Boolean =
+ this@MutableScatterSet.removeAll(elements)
}
}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/SieveCache.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/SieveCache.kt
index 39e2571..79e0f05 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/SieveCache.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/SieveCache.kt
@@ -26,16 +26,16 @@
private const val MaxSize = Int.MAX_VALUE.toLong() - 1
-private const val NodeInvalidLink = 0x7fff_ffff
-private const val NodeLinkMask = 0x7fff_ffffL
-private const val NodeLinksMask = 0x3fffffff_ffffffffL
-private const val NodeVisitedBit = 0x40000000_00000000L
-private const val NodeMetaMask = -0x40000000_00000000L // 0xc0000000_00000000UL.toLong()
-private const val NodeMetaAndNextMask = -0x3fffffff_80000001L // 0xc0000000_7fffffffUL.toLong()
-private const val NodeMetaAndPreviousMask = -0x00000000_80000000L // 0xffffffff_80000000UL.toLong()
+@PublishedApi internal const val NodeInvalidLink: Int = 0x7fff_ffff
+@PublishedApi internal const val NodeLinkMask: Long = 0x7fff_ffffL
+internal const val NodeLinksMask = 0x3fffffff_ffffffffL
+internal const val NodeVisitedBit = 0x40000000_00000000L
+internal const val NodeMetaMask = -0x40000000_00000000L // 0xc0000000_00000000UL.toLong()
+internal const val NodeMetaAndNextMask = -0x3fffffff_80000001L // 0xc0000000_7fffffffUL.toLong()
+internal const val NodeMetaAndPreviousMask = -0x00000000_80000000L // 0xffffffff_80000000UL.toLong()
-private const val EmptyNode = 0x3fffffff_ffffffffL
-private val EmptyNodes = LongArray(0)
+internal const val EmptyNode = 0x3fffffff_ffffffffL
+internal val EmptyNodes = LongArray(0)
/**
* [SieveCache] is an in-memory cache that holds strong references to a limited number of values
@@ -267,18 +267,20 @@
values[index] = value
keys[index] = key
- moveNodeToHead(index)
-
_size += sizeOf(key, value)
if (previousValue != null) {
_size -= sizeOf(key, previousValue)
onEntryRemoved(key, previousValue, value, false)
+ trimToSize(_maxSize)
+ return previousValue
}
// TODO: We should trim to size before doing the insertion. The insertion might cause
// the underlying storage to resize unnecessarily.
trimToSize(_maxSize)
+ // We need to make sure we update the linked list after eviction
+ moveNodeToHead(index)
return previousValue
}
@@ -1015,7 +1017,7 @@
}
}
-private inline fun createLinks(node: Long, previous: Int, next: Int, mapping: IntArray): Long {
+internal inline fun createLinks(node: Long, previous: Int, next: Int, mapping: IntArray): Long {
return (node and NodeMetaMask) or
(if (previous == NodeInvalidLink) NodeInvalidLink else mapping[previous]).toLong() shl
31 or
@@ -1023,20 +1025,24 @@
}
// set meta to 0 (visited = false) and previous to NodeInvalidLink
-private inline fun createLinkToNext(next: Int) =
+internal inline fun createLinkToNext(next: Int) =
0x3fffffff_80000000L or (next.toLong() and NodeLinkMask)
-private inline fun setLinkToPrevious(node: Long, previous: Int) =
+internal inline fun setLinkToPrevious(node: Long, previous: Int) =
(node and NodeMetaAndNextMask) or ((previous.toLong() and NodeLinkMask) shl 31)
-private inline fun setLinkToNext(node: Long, next: Int) =
+internal inline fun setLinkToNext(node: Long, next: Int) =
(node and NodeMetaAndPreviousMask) or (next.toLong() and NodeLinkMask)
-private inline fun clearVisitedBit(node: Long) = node and NodeLinksMask
+internal inline fun clearVisitedBit(node: Long) = node and NodeLinksMask
-private inline val Long.previousNode: Int
+@PublishedApi
+internal inline val Long.previousNode: Int
get() = ((this shr 31) and NodeLinkMask).toInt()
-private inline val Long.nextNode: Int
+
+@PublishedApi
+internal inline val Long.nextNode: Int
get() = (this and NodeLinkMask).toInt()
-private inline val Long.visited: Int
+
+internal inline val Long.visited: Int
get() = ((this shr 62) and 0x1).toInt()
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/OrderedScatterSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/OrderedScatterSetTest.kt
new file mode 100644
index 0000000..86a6d13
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/OrderedScatterSetTest.kt
@@ -0,0 +1,1270 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNull
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+class OrderedScatterSetTest {
+ @Test
+ fun emptyScatterSetConstructor() {
+ val set = MutableOrderedScatterSet<String>()
+ assertEquals(7, set.capacity)
+ assertEquals(0, set.size)
+ }
+
+ @Test
+ fun immutableEmptyScatterSet() {
+ val set = emptyOrderedScatterSet<String>()
+ assertEquals(0, set.capacity)
+ assertEquals(0, set.size)
+ }
+
+ @Test
+ fun zeroCapacityScatterSet() {
+ val set = MutableOrderedScatterSet<String>(0)
+ assertEquals(0, set.capacity)
+ assertEquals(0, set.size)
+ }
+
+ @Test
+ fun emptyScatterSetWithCapacity() {
+ // When unloading the suggested capacity, we'll fall outside of the
+ // expected bucket of 2047 entries, and we'll get 4095 instead
+ val set = MutableOrderedScatterSet<String>(1800)
+ assertEquals(4095, set.capacity)
+ assertEquals(0, set.size)
+ }
+
+ @Test
+ fun mutableScatterSetBuilder() {
+ val empty = mutableOrderedScatterSetOf<String>()
+ assertEquals(0, empty.size)
+
+ val withElements = mutableOrderedScatterSetOf("Hello", "World")
+ assertEquals(2, withElements.size)
+ assertTrue("Hello" in withElements)
+ assertTrue("World" in withElements)
+ }
+
+ @Test
+ fun addToScatterSet() {
+ val set = MutableOrderedScatterSet<String>()
+ set += "Hello"
+ assertTrue(set.add("World"))
+
+ assertEquals(2, set.size)
+ val elements = Array(2) { "" }
+ var index = 0
+ set.forEach { element -> elements[index++] = element }
+ elements.sort()
+ assertEquals("Hello", elements[0])
+ assertEquals("World", elements[1])
+ }
+
+ @Test
+ fun addToSizedScatterSet() {
+ val set = MutableOrderedScatterSet<String>(12)
+ set += "Hello"
+
+ assertEquals(1, set.size)
+ assertEquals("Hello", set.first())
+ }
+
+ @Test
+ fun addExistingElement() {
+ val set = MutableOrderedScatterSet<String>(12)
+ set += "Hello"
+ assertFalse(set.add("Hello"))
+ set += "Hello"
+
+ assertEquals(1, set.size)
+ assertEquals("Hello", set.first())
+ }
+
+ @Test
+ fun addAllArray() {
+ val set = mutableOrderedScatterSetOf("Hello")
+ assertFalse(set.addAll(arrayOf("Hello")))
+ assertEquals(1, set.size)
+ assertTrue(set.addAll(arrayOf("Hello", "World")))
+ assertEquals(2, set.size)
+ assertTrue("World" in set)
+ }
+
+ @Test
+ fun addAllIterable() {
+ val set = mutableOrderedScatterSetOf("Hello")
+ assertFalse(set.addAll(listOf("Hello")))
+ assertEquals(1, set.size)
+ assertTrue(set.addAll(listOf("Hello", "World")))
+ assertEquals(2, set.size)
+ assertTrue("World" in set)
+ }
+
+ @Test
+ fun addAllSequence() {
+ val set = mutableOrderedScatterSetOf("Hello")
+ assertFalse(set.addAll(listOf("Hello").asSequence()))
+ assertEquals(1, set.size)
+ assertTrue(set.addAll(listOf("Hello", "World").asSequence()))
+ assertEquals(2, set.size)
+ assertTrue("World" in set)
+ }
+
+ @Test
+ fun addAllScatterSet() {
+ val set = mutableOrderedScatterSetOf("Hello")
+ assertFalse(set.addAll(mutableOrderedScatterSetOf("Hello")))
+ assertEquals(1, set.size)
+ assertTrue(set.addAll(mutableOrderedScatterSetOf("Hello", "World")))
+ assertEquals(2, set.size)
+ assertTrue("World" in set)
+ }
+
+ @Test
+ fun addAllObjectList() {
+ val set = mutableOrderedScatterSetOf("Hello")
+ assertFalse(set.addAll(objectListOf("Hello")))
+ assertEquals(1, set.size)
+ assertTrue(set.addAll(objectListOf("Hello", "World")))
+ assertEquals(2, set.size)
+ assertTrue("World" in set)
+ }
+
+ @Test
+ fun plusAssignArray() {
+ val set = mutableOrderedScatterSetOf("Hello")
+ set += arrayOf("Hello")
+ assertEquals(1, set.size)
+ set += arrayOf("Hello", "World")
+ assertEquals(2, set.size)
+ assertTrue("World" in set)
+ }
+
+ @Test
+ fun plusAssignIterable() {
+ val set = mutableOrderedScatterSetOf("Hello")
+ set += listOf("Hello")
+ assertEquals(1, set.size)
+ set += listOf("Hello", "World")
+ assertEquals(2, set.size)
+ assertTrue("World" in set)
+ }
+
+ @Test
+ fun plusAssignSequence() {
+ val set = mutableOrderedScatterSetOf("Hello")
+ set += listOf("Hello").asSequence()
+ assertEquals(1, set.size)
+ set += listOf("Hello", "World").asSequence()
+ assertEquals(2, set.size)
+ assertTrue("World" in set)
+ }
+
+ @Test
+ fun plusAssignScatterSet() {
+ val set = mutableOrderedScatterSetOf("Hello")
+ set += mutableOrderedScatterSetOf("Hello")
+ assertEquals(1, set.size)
+ set += mutableOrderedScatterSetOf("Hello", "World")
+ assertEquals(2, set.size)
+ assertTrue("World" in set)
+ }
+
+ @Test
+ fun plusAssignObjectList() {
+ val set = mutableOrderedScatterSetOf("Hello")
+ set += objectListOf("Hello")
+ assertEquals(1, set.size)
+ set += objectListOf("Hello", "World")
+ assertEquals(2, set.size)
+ assertTrue("World" in set)
+ }
+
+ @Test
+ fun nullElement() {
+ val set = MutableOrderedScatterSet<String?>()
+ set += null
+
+ assertEquals(1, set.size)
+ assertNull(set.first())
+ }
+
+ @Test
+ fun firstWithValue() {
+ val set = MutableOrderedScatterSet<String>()
+ set += "Hello"
+ set += "World"
+ var element: String? = null
+ var otherElement: String? = null
+ set.forEach { if (element == null) element = it else otherElement = it }
+ assertEquals(element, set.first())
+ set -= element!!
+ assertEquals(otherElement, set.first())
+ }
+
+ @Test
+ fun firstEmpty() {
+ assertFailsWith(NoSuchElementException::class) {
+ val set = MutableOrderedScatterSet<String>()
+ set.first()
+ }
+ }
+
+ @Test
+ fun firstMatching() {
+ val set = MutableOrderedScatterSet<String>()
+ set += "Hello"
+ set += "Hallo"
+ set += "World"
+ set += "Welt"
+ assertEquals("Hello", set.first { it.contains('H') })
+ assertEquals("World", set.first { it.contains('W') })
+ }
+
+ @Test
+ fun firstMatchingEmpty() {
+ assertFailsWith(NoSuchElementException::class) {
+ val set = MutableOrderedScatterSet<String>()
+ set.first { it.contains('H') }
+ }
+ }
+
+ @Test
+ fun firstMatchingNoMatch() {
+ assertFailsWith(NoSuchElementException::class) {
+ val set = MutableOrderedScatterSet<String>()
+ set += "Hello"
+ set += "World"
+ set.first { it.startsWith("Q") }
+ }
+ }
+
+ @Test
+ fun firstOrNull() {
+ val set = MutableOrderedScatterSet<String>()
+ assertNull(set.firstOrNull { it.startsWith('H') })
+ set += "Hello"
+ set += "Hallo"
+ set += "World"
+ set += "Welt"
+ var element: String? = null
+ set.forEach { if (element == null) element = it }
+ assertEquals(element, set.firstOrNull { it.contains('l') })
+ assertEquals("Hello", set.firstOrNull { it.contains('H') })
+ assertEquals("World", set.firstOrNull { it.contains('W') })
+ assertNull(set.firstOrNull { it.startsWith('Q') })
+ }
+
+ @Test
+ fun lastWithValue() {
+ val set = MutableOrderedScatterSet<String>()
+ set += "Hello"
+ set += "World"
+ var element: String? = null
+ var otherElement: String? = null
+ set.forEach { if (otherElement == null) otherElement = it else element = it }
+ assertEquals(element, set.last())
+ set -= element!!
+ assertEquals(otherElement, set.last())
+ }
+
+ @Test
+ fun lastEmpty() {
+ assertFailsWith(NoSuchElementException::class) {
+ val set = MutableOrderedScatterSet<String>()
+ set.last()
+ }
+ }
+
+ @Test
+ fun lastMatching() {
+ val set = MutableOrderedScatterSet<String>()
+ set += "Hello"
+ set += "Hallo"
+ set += "World"
+ set += "Welt"
+ assertEquals("Hallo", set.last { it.contains('H') })
+ assertEquals("Welt", set.last { it.contains('W') })
+ }
+
+ @Test
+ fun lastMatchingEmpty() {
+ assertFailsWith(NoSuchElementException::class) {
+ val set = MutableOrderedScatterSet<String>()
+ set.last { it.contains('H') }
+ }
+ }
+
+ @Test
+ fun lastMatchingNoMatch() {
+ assertFailsWith(NoSuchElementException::class) {
+ val set = MutableOrderedScatterSet<String>()
+ set += "Hello"
+ set += "World"
+ set.last { it.startsWith("Q") }
+ }
+ }
+
+ @Test
+ fun lastOrNull() {
+ val set = MutableOrderedScatterSet<String>()
+ assertNull(set.lastOrNull { it.startsWith('H') })
+ set += "Hello"
+ set += "Hallo"
+ set += "World"
+ set += "Welt"
+ var element: String? = null
+ set.forEachReverse { if (element == null) element = it }
+ assertEquals(element, set.lastOrNull { it.contains('l') })
+ assertEquals("Hallo", set.lastOrNull { it.contains('H') })
+ assertEquals("Welt", set.lastOrNull { it.contains('W') })
+ assertNull(set.lastOrNull { it.startsWith('Q') })
+ }
+
+ @Test
+ fun remove() {
+ val set = MutableOrderedScatterSet<String?>()
+ assertFalse(set.remove("Hello"))
+
+ set += "Hello"
+ assertTrue(set.remove("Hello"))
+ assertEquals(0, set.size)
+
+ set += "Hello"
+ set -= "Hello"
+ assertEquals(0, set.size)
+
+ set += null
+ assertTrue(set.remove(null))
+ assertEquals(0, set.size)
+
+ set += null
+ set -= null
+ assertEquals(0, set.size)
+ }
+
+ @Test
+ fun removeThenAdd() {
+ // Use a size of 6 to fit in a single entry in the metadata table
+ val set = MutableOrderedScatterSet<String?>(6)
+ set += "Hello"
+ set += "Bonjour"
+ set += "Hallo"
+ set += "Konnichiwa"
+ set += "Ciao"
+ set += "Annyeong"
+
+ // Removing all the entries will mark the medata as deleted
+ set.remove("Hello")
+ set.remove("Bonjour")
+ set.remove("Hallo")
+ set.remove("Konnichiwa")
+ set.remove("Ciao")
+ set.remove("Annyeong")
+
+ assertEquals(0, set.size)
+
+ val capacity = set.capacity
+
+ // Make sure reinserting an entry after filling the table
+ // with "Deleted" markers works
+ set += "Hello"
+
+ assertEquals(1, set.size)
+ assertEquals(capacity, set.capacity)
+ }
+
+ @Test
+ fun removeAllArray() {
+ val set = mutableOrderedScatterSetOf("Hello", "World")
+ assertFalse(set.removeAll(arrayOf("Hola", "Bonjour")))
+ assertEquals(2, set.size)
+ assertTrue(set.removeAll(arrayOf("Hola", "Hello", "Bonjour")))
+ assertEquals(1, set.size)
+ assertFalse("Hello" in set)
+ }
+
+ @Test
+ fun removeAllIterable() {
+ val set = mutableOrderedScatterSetOf("Hello", "World")
+ assertFalse(set.removeAll(listOf("Hola", "Bonjour")))
+ assertEquals(2, set.size)
+ assertTrue(set.removeAll(listOf("Hola", "Hello", "Bonjour")))
+ assertEquals(1, set.size)
+ assertFalse("Hello" in set)
+ }
+
+ @Test
+ fun removeAllSequence() {
+ val set = mutableOrderedScatterSetOf("Hello", "World")
+ assertFalse(set.removeAll(sequenceOf("Hola", "Bonjour")))
+ assertEquals(2, set.size)
+ assertTrue(set.removeAll(sequenceOf("Hola", "Hello", "Bonjour")))
+ assertEquals(1, set.size)
+ assertFalse("Hello" in set)
+ }
+
+ @Test
+ fun removeAllScatterSet() {
+ val set = mutableOrderedScatterSetOf("Hello", "World")
+ assertFalse(set.removeAll(mutableOrderedScatterSetOf("Hola", "Bonjour")))
+ assertEquals(2, set.size)
+ assertTrue(set.removeAll(mutableOrderedScatterSetOf("Hola", "Hello", "Bonjour")))
+ assertEquals(1, set.size)
+ assertFalse("Hello" in set)
+ }
+
+ @Test
+ fun removeAllObjectList() {
+ val set = mutableOrderedScatterSetOf("Hello", "World")
+ assertFalse(set.removeAll(objectListOf("Hola", "Bonjour")))
+ assertEquals(2, set.size)
+ assertTrue(set.removeAll(objectListOf("Hola", "Hello", "Bonjour")))
+ assertEquals(1, set.size)
+ assertFalse("Hello" in set)
+ }
+
+ @Test
+ fun removeDoesNotCauseGrowthOnInsert() {
+ val set = MutableOrderedScatterSet<String>(10) // Must be > GroupWidth (8)
+ assertEquals(15, set.capacity)
+
+ set += "Hello"
+ set += "Bonjour"
+ set += "Hallo"
+ set += "Konnichiwa"
+ set += "Ciao"
+ set += "Annyeong"
+
+ // Reach the upper limit of what we can store without increasing the map size
+ for (i in 0..7) {
+ set += i.toString()
+ }
+
+ // Delete a few items
+ for (i in 0..5) {
+ set.remove(i.toString())
+ }
+
+ // Inserting a new item shouldn't cause growth, but the deleted markers to be purged
+ set += "Foo"
+ assertEquals(15, set.capacity)
+
+ assertTrue(set.contains("Foo"))
+ }
+
+ @Test
+ fun minusAssignArray() {
+ val set = mutableOrderedScatterSetOf("Hello", "World")
+ set -= arrayOf("Hola", "Bonjour")
+ assertEquals(2, set.size)
+ set -= arrayOf("Hola", "Hello", "Bonjour")
+ assertEquals(1, set.size)
+ assertFalse("Hello" in set)
+ }
+
+ @Test
+ fun minusAssignIterable() {
+ val set = mutableOrderedScatterSetOf("Hello", "World")
+ set -= listOf("Hola", "Bonjour")
+ assertEquals(2, set.size)
+ set -= listOf("Hola", "Hello", "Bonjour")
+ assertEquals(1, set.size)
+ assertFalse("Hello" in set)
+ }
+
+ @Test
+ fun minusAssignSequence() {
+ val set = mutableOrderedScatterSetOf("Hello", "World")
+ set -= sequenceOf("Hola", "Bonjour")
+ assertEquals(2, set.size)
+ set -= sequenceOf("Hola", "Hello", "Bonjour")
+ assertEquals(1, set.size)
+ assertFalse("Hello" in set)
+ }
+
+ @Test
+ fun minusAssignScatterSet() {
+ val set = mutableOrderedScatterSetOf("Hello", "World")
+ set -= mutableOrderedScatterSetOf("Hola", "Bonjour")
+ assertEquals(2, set.size)
+ set -= mutableOrderedScatterSetOf("Hola", "Hello", "Bonjour")
+ assertEquals(1, set.size)
+ assertFalse("Hello" in set)
+ }
+
+ @Test
+ fun minusAssignObjectList() {
+ val set = mutableOrderedScatterSetOf("Hello", "World")
+ set -= objectListOf("Hola", "Bonjour")
+ assertEquals(2, set.size)
+ set -= objectListOf("Hola", "Hello", "Bonjour")
+ assertEquals(1, set.size)
+ assertFalse("Hello" in set)
+ }
+
+ @Test
+ fun insertManyEntries() {
+ val set = MutableOrderedScatterSet<String>()
+
+ for (i in 0 until 1700) {
+ set += i.toString()
+ }
+
+ assertEquals(1700, set.size)
+ }
+
+ @Test
+ fun forEach() {
+ for (i in 8..24) {
+ val set = MutableOrderedScatterSet<Int>()
+
+ for (j in 0 until i) {
+ set += j
+ }
+
+ val elements = Array(i) { -1 }
+ var index = 0
+ set.forEach { element ->
+ println(element)
+ elements[index++] = element
+ }
+
+ index = 0
+ elements.forEach { element ->
+ assertEquals(element, index)
+ index++
+ }
+ }
+ }
+
+ @Test
+ fun forEachIsOrdered() {
+ val expected =
+ mutableListOf(
+ "Hello",
+ "World",
+ "Hola",
+ "Mundo",
+ "Bonjour",
+ "Monde",
+ "Hallo",
+ )
+ val set = mutableOrderedScatterSetOf<String>()
+
+ set += expected
+ assertContentEquals(expected, set.toList())
+
+ // Removing an item should preserve order
+ expected -= "Hallo"
+ set -= "Hallo"
+ assertContentEquals(expected, set.toList())
+
+ // Adding an item should preserve order
+ expected += "Barev"
+ set += "Barev"
+ assertContentEquals(expected, set.toList())
+
+ // Causing a resize should preserve order
+ val extra = listOf("Welt", "Konnichiwa", "Sekai", "Ciao", "Mondo", "Annyeong", "Sesang")
+ expected += extra
+ set += extra
+ assertContentEquals(expected, set.toList())
+
+ // Trimming preserves order
+ set.trim()
+ assertContentEquals(expected, set.toList())
+
+ // Trimming to a new size preserves order
+ expected.clear()
+ expected += listOf("Hello", "World", "Hola", "Mundo", "Bonjour", "Monde", "Barev")
+ set.trimToSize(7)
+ assertContentEquals(expected, set.toList())
+
+ set.clear()
+ assertContentEquals(listOf(), set.toList())
+ }
+
+ @Test
+ fun iteratorIsOrdered() {
+ val expected =
+ mutableListOf(
+ "Hello",
+ "World",
+ "Hola",
+ "Mundo",
+ "Bonjour",
+ "Monde",
+ "Hallo",
+ )
+ val set = mutableOrderedScatterSetOf<String>()
+
+ set += expected
+ assertContentEquals(expected, set.asMutableSet().iterator().asSequence().toList())
+ assertContentEquals(expected, set.asSet().iterator().asSequence().toList())
+
+ // Removing an item should preserve order
+ expected -= "Hallo"
+ set -= "Hallo"
+ assertContentEquals(expected, set.asMutableSet().iterator().asSequence().toList())
+ assertContentEquals(expected, set.asSet().iterator().asSequence().toList())
+
+ // Adding an item should preserve order
+ expected += "Barev"
+ set += "Barev"
+ assertContentEquals(expected, set.asMutableSet().iterator().asSequence().toList())
+ assertContentEquals(expected, set.asSet().iterator().asSequence().toList())
+
+ // Causing a resize should preserve order
+ val extra = listOf("Welt", "Konnichiwa", "Sekai", "Ciao", "Mondo", "Annyeong", "Sesang")
+ expected += extra
+ set += extra
+ assertContentEquals(expected, set.asMutableSet().iterator().asSequence().toList())
+ assertContentEquals(expected, set.asSet().iterator().asSequence().toList())
+
+ // Trimming preserves order
+ set.trim()
+ assertContentEquals(expected, set.asMutableSet().iterator().asSequence().toList())
+ assertContentEquals(expected, set.asSet().iterator().asSequence().toList())
+
+ // Trimming to a new size preserves order
+ expected.clear()
+ expected += listOf("Hello", "World", "Hola", "Mundo", "Bonjour", "Monde", "Barev")
+ set.trimToSize(7)
+ assertContentEquals(expected, set.asMutableSet().iterator().asSequence().toList())
+ assertContentEquals(expected, set.asSet().iterator().asSequence().toList())
+
+ set.clear()
+ assertContentEquals(listOf(), set.asMutableSet().iterator().asSequence().toList())
+ assertContentEquals(listOf(), set.asSet().iterator().asSequence().toList())
+ }
+
+ @Test
+ fun trimToSize() {
+ val set =
+ mutableOrderedScatterSetOf(
+ "Hello",
+ "World",
+ "Hola",
+ "Mundo",
+ "Bonjour",
+ "Monde",
+ "Hallo"
+ )
+
+ val size = set.size
+ set.trimToSize(size)
+ assertEquals(size, set.size)
+
+ set.trimToSize(4)
+ assertEquals(4, set.size)
+
+ set.trimToSize(17)
+ assertEquals(4, set.size)
+
+ set.trimToSize(0)
+ assertEquals(0, set.size)
+
+ set += listOf("Hello", "World", "Hola", "Mundo", "Bonjour", "Monde", "Hallo")
+ set.trimToSize(-1)
+ assertEquals(0, set.size)
+ }
+
+ @Test
+ fun clear() {
+ val set = MutableOrderedScatterSet<String>()
+
+ for (i in 0 until 32) {
+ set += i.toString()
+ }
+
+ val capacity = set.capacity
+ set.clear()
+
+ assertEquals(0, set.size)
+ assertEquals(capacity, set.capacity)
+
+ set.forEach { fail() }
+ }
+
+ @Test
+ fun string() {
+ val set = MutableOrderedScatterSet<String?>()
+ assertEquals("[]", set.toString())
+
+ set += "Hello"
+ set += "Bonjour"
+ assertTrue("[Hello, Bonjour]" == set.toString() || "[Bonjour, Hello]" == set.toString())
+
+ set.clear()
+ set += null
+ assertEquals("[null]", set.toString())
+
+ set.clear()
+
+ val selfAsElement = MutableOrderedScatterSet<Any>()
+ selfAsElement.add(selfAsElement)
+ assertEquals("[(this)]", selfAsElement.toString())
+ }
+
+ @Test
+ fun joinToString() {
+ val set = scatterSetOf(1, 2, 3, 4, 5)
+ val order = IntArray(5)
+ var index = 0
+ set.forEach { element -> order[index++] = element }
+ assertEquals(
+ "${order[0]}, ${order[1]}, ${order[2]}, ${order[3]}, ${order[4]}",
+ set.joinToString()
+ )
+ assertEquals(
+ "x${order[0]}, ${order[1]}, ${order[2]}...",
+ set.joinToString(prefix = "x", postfix = "y", limit = 3)
+ )
+ assertEquals(
+ ">${order[0]}-${order[1]}-${order[2]}-${order[3]}-${order[4]}<",
+ set.joinToString(separator = "-", prefix = ">", postfix = "<")
+ )
+ val names = arrayOf("one", "two", "three", "four", "five")
+ assertEquals(
+ "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+ set.joinToString(limit = 3) { names[it] }
+ )
+ }
+
+ @Test
+ fun hashCodeAddValues() {
+ val set = mutableOrderedScatterSetOf<String?>()
+ assertEquals(217, set.hashCode())
+ set += null
+ assertEquals(218, set.hashCode())
+ set += "Hello"
+ val h1 = set.hashCode()
+ set += "World"
+ assertNotEquals(h1, set.hashCode())
+ }
+
+ @Test
+ fun equals() {
+ val set = MutableOrderedScatterSet<String?>()
+ set += "Hello"
+ set += null
+ set += "Bonjour"
+
+ assertFalse(set.equals(null))
+ assertEquals(set, set)
+
+ val set2 = MutableOrderedScatterSet<String?>()
+ set2 += "Bonjour"
+ set2 += null
+
+ assertNotEquals(set, set2)
+
+ set2 += "Hello"
+ assertEquals(set, set2)
+ }
+
+ @Test
+ fun contains() {
+ val set = MutableOrderedScatterSet<String?>()
+ set += "Hello"
+ set += null
+ set += "Bonjour"
+
+ assertTrue(set.contains("Hello"))
+ assertTrue(set.contains(null))
+ assertFalse(set.contains("World"))
+ }
+
+ @Test
+ fun empty() {
+ val set = MutableOrderedScatterSet<String?>()
+ assertTrue(set.isEmpty())
+ assertFalse(set.isNotEmpty())
+ assertTrue(set.none())
+ assertFalse(set.any())
+
+ set += "Hello"
+
+ assertFalse(set.isEmpty())
+ assertTrue(set.isNotEmpty())
+ assertTrue(set.any())
+ assertFalse(set.none())
+ }
+
+ @Test
+ fun count() {
+ val set = MutableOrderedScatterSet<String>()
+ assertEquals(0, set.count())
+
+ set += "Hello"
+ assertEquals(1, set.count())
+
+ set += "Bonjour"
+ set += "Hallo"
+ set += "Konnichiwa"
+ set += "Ciao"
+ set += "Annyeong"
+
+ assertEquals(2, set.count { it.startsWith("H") })
+ assertEquals(0, set.count { it.startsWith("W") })
+ }
+
+ @Test
+ fun any() {
+ val set = MutableOrderedScatterSet<String>()
+ set += "Hello"
+ set += "Bonjour"
+ set += "Hallo"
+ set += "Konnichiwa"
+ set += "Ciao"
+ set += "Annyeong"
+
+ assertTrue(set.any { it.startsWith("K") })
+ assertFalse(set.any { it.startsWith("W") })
+ }
+
+ @Test
+ fun all() {
+ val set = MutableOrderedScatterSet<String>()
+ set += "Hello"
+ set += "Bonjour"
+ set += "Hallo"
+ set += "Konnichiwa"
+ set += "Ciao"
+ set += "Annyeong"
+
+ assertTrue(set.all { it.length >= 4 })
+ assertFalse(set.all { it.length >= 5 })
+ }
+
+ @Test
+ fun asSet() {
+ val scatterSet = mutableOrderedScatterSetOf("Hello", "World")
+ val set = scatterSet.asSet()
+ assertEquals(2, set.size)
+ assertTrue(set.containsAll(listOf("Hello", "World")))
+ assertFalse(set.containsAll(listOf("Hola", "World")))
+ assertTrue(set.contains("Hello"))
+ assertTrue(set.contains("World"))
+ assertFalse(set.contains("Hola"))
+ assertFalse(set.isEmpty())
+ val elements = Array(2) { "" }
+ set.forEachIndexed { index, element -> elements[index] = element }
+ elements.sort()
+ assertEquals("Hello", elements[0])
+ assertEquals("World", elements[1])
+ }
+
+ @Test
+ fun asMutableSet() {
+ val scatterSet = mutableOrderedScatterSetOf("Hello", "World")
+ val set = scatterSet.asMutableSet()
+ assertTrue("Hello" in set)
+ assertTrue("World" in set)
+ assertFalse("Bonjour" in set)
+
+ assertFalse(set.add("Hello"))
+ assertEquals(2, set.size)
+
+ assertTrue(set.add("Hola"))
+ assertEquals(3, set.size)
+ assertTrue("Hola" in set)
+
+ assertFalse(set.addAll(listOf("World", "Hello")))
+ assertEquals(3, set.size)
+
+ assertTrue(set.addAll(listOf("Hello", "Mundo")))
+ assertEquals(4, set.size)
+ assertTrue("Mundo" in set)
+
+ assertFalse(set.remove("Bonjour"))
+ assertEquals(4, set.size)
+
+ assertTrue(set.remove("World"))
+ assertEquals(3, set.size)
+ assertFalse("World" in set)
+
+ assertFalse(set.retainAll(listOf("Hola", "Hello", "Mundo")))
+ assertEquals(3, set.size)
+
+ assertTrue(set.retainAll(listOf("Hola", "Hello")))
+ assertEquals(2, set.size)
+ assertFalse("Mundo" in set)
+
+ assertFalse(set.removeAll(listOf("Bonjour", "Mundo")))
+ assertEquals(2, set.size)
+
+ assertTrue(set.removeAll(listOf("Hello", "Mundo")))
+ assertEquals(1, set.size)
+ assertFalse("Hello" in set)
+
+ set.clear()
+ assertEquals(0, set.size)
+ assertFalse("Hola" in set)
+ }
+
+ @Test
+ fun trim() {
+ val set = mutableOrderedScatterSetOf("Hello", "World", "Hola", "Mundo", "Bonjour", "Monde")
+ val capacity = set.capacity
+ assertEquals(0, set.trim())
+ set.clear()
+ assertEquals(capacity, set.trim())
+ assertEquals(0, set.capacity)
+ set.addAll(
+ arrayOf(
+ "Hello",
+ "World",
+ "Hola",
+ "Mundo",
+ "Bonjour",
+ "Monde",
+ "Hallo",
+ "Welt",
+ "Konnichiwa",
+ "Sekai",
+ "Ciao",
+ "Mondo",
+ "Annyeong",
+ "Sesang"
+ )
+ )
+ set.removeAll(
+ arrayOf("Hallo", "Welt", "Konnichiwa", "Sekai", "Ciao", "Mondo", "Annyeong", "Sesang")
+ )
+ assertTrue(set.trim() > 0)
+ assertEquals(capacity, set.capacity)
+ }
+
+ @Test
+ fun scatterSetOfEmpty() {
+ assertSame(emptyScatterSet(), scatterSetOf<String>())
+ assertEquals(0, scatterSetOf<String>().size)
+ }
+
+ @Test
+ fun scatterSetOfOne() {
+ val set = scatterSetOf("Hello")
+ assertEquals(1, set.size)
+ assertEquals("Hello", set.first())
+ }
+
+ @Test
+ fun scatterSetOfTwo() {
+ val set = scatterSetOf("Hello", "World")
+ assertEquals(2, set.size)
+ assertTrue("Hello" in set)
+ assertTrue("World" in set)
+ assertFalse("Bonjour" in set)
+ }
+
+ @Test
+ fun scatterSetOfThree() {
+ val set = scatterSetOf("Hello", "World", "Hola")
+ assertEquals(3, set.size)
+ assertTrue("Hello" in set)
+ assertTrue("World" in set)
+ assertTrue("Hola" in set)
+ assertFalse("Bonjour" in set)
+ }
+
+ @Test
+ fun scatterSetOfFour() {
+ val set = scatterSetOf("Hello", "World", "Hola", "Mundo")
+ assertEquals(4, set.size)
+ assertTrue("Hello" in set)
+ assertTrue("World" in set)
+ assertTrue("Hola" in set)
+ assertTrue("Mundo" in set)
+ assertFalse("Bonjour" in set)
+ }
+
+ @Test
+ fun mutableOrderedScatterSetOfOne() {
+ val set = mutableOrderedScatterSetOf("Hello")
+ assertEquals(1, set.size)
+ assertEquals("Hello", set.first())
+ }
+
+ @Test
+ fun mutableOrderedScatterSetOfTwo() {
+ val set = mutableOrderedScatterSetOf("Hello", "World")
+ assertEquals(2, set.size)
+ assertTrue("Hello" in set)
+ assertTrue("World" in set)
+ assertFalse("Bonjour" in set)
+ }
+
+ @Test
+ fun mutableOrderedScatterSetOfThree() {
+ val set = mutableOrderedScatterSetOf("Hello", "World", "Hola")
+ assertEquals(3, set.size)
+ assertTrue("Hello" in set)
+ assertTrue("World" in set)
+ assertTrue("Hola" in set)
+ assertFalse("Bonjour" in set)
+ }
+
+ @Test
+ fun mutableOrderedScatterSetOfFour() {
+ val set = mutableOrderedScatterSetOf("Hello", "World", "Hola", "Mundo")
+ assertEquals(4, set.size)
+ assertTrue("Hello" in set)
+ assertTrue("World" in set)
+ assertTrue("Hola" in set)
+ assertTrue("Mundo" in set)
+ assertFalse("Bonjour" in set)
+ }
+
+ @Test
+ fun removeIf() {
+ val set = MutableOrderedScatterSet<String>()
+ set.add("Hello")
+ set.add("Bonjour")
+ set.add("Hallo")
+ set.add("Konnichiwa")
+ set.add("Ciao")
+ set.add("Annyeong")
+
+ set.removeIf { value -> value.startsWith('H') }
+
+ assertEquals(4, set.size)
+ assertTrue(set.contains("Bonjour"))
+ assertTrue(set.contains("Konnichiwa"))
+ assertTrue(set.contains("Ciao"))
+ assertTrue(set.contains("Annyeong"))
+ }
+
+ @Test
+ fun insertOneRemoveOne() {
+ val set = MutableOrderedScatterSet<Int>()
+
+ for (i in 0..1000000) {
+ set.add(i)
+ set.remove(i)
+ assertTrue(set.capacity < 16, "Set grew larger than 16 after step $i")
+ }
+ }
+
+ @Test
+ fun insertManyRemoveMany() {
+ val map = MutableOrderedScatterSet<String>()
+
+ for (i in 0..100) {
+ map.add(i.toString())
+ }
+
+ for (i in 0..100) {
+ if (i % 2 == 0) {
+ map.remove(i.toString())
+ }
+ }
+
+ for (i in 0..100) {
+ if (i % 2 == 0) {
+ map.add(i.toString())
+ }
+ }
+
+ for (i in 0..100) {
+ if (i % 2 != 0) {
+ map.remove(i.toString())
+ }
+ }
+
+ for (i in 0..100) {
+ if (i % 2 != 0) {
+ map.add(i.toString())
+ }
+ }
+
+ assertEquals(127, map.capacity)
+ for (i in 0..100) {
+ assertTrue(map.contains(i.toString()), "Map should contain element $i")
+ }
+ }
+
+ @Test
+ fun removeWhenIterating() {
+ val set = MutableOrderedScatterSet<String>()
+ set.add("Hello")
+ set.add("Bonjour")
+ set.add("Hallo")
+ set.add("Konnichiwa")
+ set.add("Ciao")
+ set.add("Annyeong")
+
+ val iterator = set.asMutableSet().iterator()
+ while (iterator.hasNext()) {
+ iterator.next()
+ iterator.remove()
+ }
+
+ assertEquals(0, set.size)
+ }
+
+ @Test
+ fun removeWhenForEach() {
+ val set = MutableOrderedScatterSet<String>()
+ set.add("Hello")
+ set.add("Bonjour")
+ set.add("Hallo")
+ set.add("Konnichiwa")
+ set.add("Ciao")
+ set.add("Annyeong")
+
+ set.forEach { element -> set.remove(element) }
+
+ assertEquals(0, set.size)
+ }
+
+ @Test
+ fun removeWhenForEachReverse() {
+ val set = MutableOrderedScatterSet<String>()
+ set.add("Hello")
+ set.add("Bonjour")
+ set.add("Hallo")
+ set.add("Konnichiwa")
+ set.add("Ciao")
+ set.add("Annyeong")
+
+ set.forEachReverse { element -> set.remove(element) }
+
+ assertEquals(0, set.size)
+ }
+
+ @Test
+ fun retainAllCollection() {
+ val set = mutableOrderedScatterSetOf<String>()
+
+ set.retainAll(listOf())
+ assertTrue(set.isEmpty())
+
+ set.retainAll(listOf("Monde", "Hallo", "Welt", "Konnichiwa"))
+ assertTrue(set.isEmpty())
+
+ set +=
+ listOf(
+ "Hello",
+ "World",
+ "Hola",
+ "Mundo",
+ "Bonjour",
+ "Monde",
+ "Hallo",
+ "Welt",
+ "Konnichiwa",
+ "Sekai",
+ "Ciao",
+ "Mondo",
+ "Annyeong",
+ "Sesang"
+ )
+
+ val content = listOf("Monde", "Hallo", "Sekai", "Ciao")
+ set.retainAll(content)
+ assertContentEquals(content, set.toList())
+
+ set.retainAll(listOf())
+ assertTrue(set.isEmpty())
+ set.forEach { fail() }
+ }
+
+ @Test
+ fun retainAllSet() {
+ val set = mutableOrderedScatterSetOf<String>()
+
+ set.retainAll(listOf())
+ assertTrue(set.isEmpty())
+
+ set.retainAll(orderedScatterSetOf("Monde", "Hallo", "Welt", "Konnichiwa"))
+ assertTrue(set.isEmpty())
+
+ set +=
+ listOf(
+ "Hello",
+ "World",
+ "Hola",
+ "Mundo",
+ "Bonjour",
+ "Monde",
+ "Hallo",
+ "Welt",
+ "Konnichiwa",
+ "Sekai",
+ "Ciao",
+ "Mondo",
+ "Annyeong",
+ "Sesang"
+ )
+
+ val content = orderedScatterSetOf("Monde", "Hallo", "Sekai", "Ciao")
+ set.retainAll(content)
+ assertContentEquals(content.toList(), set.toList())
+
+ set.retainAll(listOf())
+ assertTrue(set.isEmpty())
+ set.forEach { fail() }
+ }
+
+ @Test
+ fun retainAllPredicate() {
+ val set = mutableOrderedScatterSetOf<String>()
+
+ set.retainAll { false }
+ assertTrue(set.isEmpty())
+
+ set.retainAll { true }
+ assertTrue(set.isEmpty())
+
+ set +=
+ listOf(
+ "Hello",
+ "World",
+ "Hola",
+ "Mundo",
+ "Bonjour",
+ "Monde",
+ "Hallo",
+ "Welt",
+ "Konnichiwa",
+ "Sekai",
+ "Ciao",
+ "Mondo",
+ "Annyeong",
+ "Sesang"
+ )
+
+ val content = listOf("World", "Welt")
+ set.retainAll { it.startsWith('W') }
+ assertContentEquals(content.toList(), set.toList())
+
+ set.retainAll { false }
+ assertTrue(set.isEmpty())
+ set.forEach { fail() }
+ }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt
index 108548e..dad4c38 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt
@@ -25,6 +25,7 @@
import kotlin.test.assertNull
import kotlin.test.assertSame
import kotlin.test.assertTrue
+import kotlin.test.fail
class ScatterMapTest {
@Test
@@ -626,6 +627,8 @@
assertEquals(0, map.size)
assertEquals(capacity, map.capacity)
+
+ map.forEach { _, _ -> fail() }
}
@Test
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt
index 5185166..a527025 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt
@@ -550,13 +550,13 @@
@Test
fun hashCodeAddValues() {
val set = mutableScatterSetOf<String?>()
- assertEquals(0, set.hashCode())
+ assertEquals(217, set.hashCode())
set += null
- assertEquals(0, set.hashCode())
+ assertEquals(218, set.hashCode())
set += "Hello"
- assertEquals("Hello".hashCode(), set.hashCode())
+ val h1 = set.hashCode()
set += "World"
- assertEquals("World".hashCode() + "Hello".hashCode(), set.hashCode())
+ assertNotEquals(h1, set.hashCode())
}
@Test
@@ -899,4 +899,38 @@
assertTrue(map.contains(i), "Map should contain element $i")
}
}
+
+ @Test
+ fun removeWhenIterating() {
+ val set = MutableScatterSet<String>()
+ set.add("Hello")
+ set.add("Bonjour")
+ set.add("Hallo")
+ set.add("Konnichiwa")
+ set.add("Ciao")
+ set.add("Annyeong")
+
+ val iterator = set.asMutableSet().iterator()
+ while (iterator.hasNext()) {
+ iterator.next()
+ iterator.remove()
+ }
+
+ assertEquals(0, set.size)
+ }
+
+ @Test
+ fun removeWhenForEach() {
+ val set = MutableScatterSet<String>()
+ set.add("Hello")
+ set.add("Bonjour")
+ set.add("Hallo")
+ set.add("Konnichiwa")
+ set.add("Ciao")
+ set.add("Annyeong")
+
+ set.forEach { element -> set.remove(element) }
+
+ assertEquals(0, set.size)
+ }
}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/SieveCacheTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/SieveCacheTest.kt
index efcec45..bbe1487 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/SieveCacheTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/SieveCacheTest.kt
@@ -856,6 +856,19 @@
assertEquals(1, cache.size)
}
+ @Test
+ fun evictionInCacheOfSize1() {
+ val cache = SieveCache<String, String>(1, 1)
+ cache["1"] = "a"
+
+ // Visit the entry
+ assertEquals("a", cache["1"])
+
+ cache["2"] = "b"
+ assertFalse("1" in cache)
+ assertEquals("b", cache["2"])
+ }
+
private fun createCreatingCache(): SieveCache<String, String> {
return SieveCache(4, createValueFromKey = { key -> "created-$key" })
}
diff --git a/compose/animation/animation-core/build.gradle b/compose/animation/animation-core/build.gradle
index 595aef5..8eac369 100644
--- a/compose/animation/animation-core/build.gradle
+++ b/compose/animation/animation-core/build.gradle
@@ -46,7 +46,7 @@
implementation(project(":compose:ui:ui-unit"))
implementation(project(":compose:ui:ui-graphics"))
implementation(project(":compose:ui:ui-util"))
- implementation(project(":collection:collection"))
+ implementation("androidx.collection:collection:1.4.2")
implementation(libs.kotlinStdlib)
api(libs.kotlinCoroutinesCore)
}
diff --git a/compose/animation/animation-graphics/build.gradle b/compose/animation/animation-graphics/build.gradle
index 16f1350..399503d 100644
--- a/compose/animation/animation-graphics/build.gradle
+++ b/compose/animation/animation-graphics/build.gradle
@@ -49,7 +49,7 @@
api(project(":compose:ui:ui-geometry"))
implementation(project(":compose:ui:ui-util"))
- implementation(project(":collection:collection"))
+ implementation("androidx.collection:collection:1.4.2")
}
}
androidMain.dependencies {
diff --git a/compose/animation/animation/build.gradle b/compose/animation/animation/build.gradle
index f968628..a2950c4 100644
--- a/compose/animation/animation/build.gradle
+++ b/compose/animation/animation/build.gradle
@@ -52,7 +52,7 @@
implementation(project(":compose:ui:ui-util"))
implementation(project(":compose:ui:ui-graphics"))
- implementation(project(":collection:collection"))
+ implementation("androidx.collection:collection:1.4.2")
}
}
diff --git a/compose/docs/compose-component-api-guidelines.md b/compose/docs/compose-component-api-guidelines.md
index 857ee3e..978d7ff 100644
--- a/compose/docs/compose-component-api-guidelines.md
+++ b/compose/docs/compose-component-api-guidelines.md
@@ -632,7 +632,7 @@
The order of parameters in a component must be as follows:
1. Required parameters.
-2. Single` modifier: Modifier = Modifier`.
+2. Single `modifier: Modifier = Modifier`.
3. Optional parameters.
4. (optional) trailing `@Composable` lambda.
diff --git a/compose/foundation/foundation-layout/benchmark/build.gradle b/compose/foundation/foundation-layout/benchmark/build.gradle
index 098eb82..8726ebd 100644
--- a/compose/foundation/foundation-layout/benchmark/build.gradle
+++ b/compose/foundation/foundation-layout/benchmark/build.gradle
@@ -41,7 +41,4 @@
android {
compileSdk 35
namespace "androidx.compose.foundation.layout.benchmark"
-
- // Experiment to observe effect of minification on noise, see b/354264743
- buildTypes.release.androidTest.enableMinification = true
}
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index 565c485..703c2f2 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -44,8 +44,8 @@
api(project(":compose:ui:ui"))
implementation(project(":compose:runtime:runtime"))
implementation(project(":compose:ui:ui-util"))
- implementation(project(":collection:collection"))
implementation(project(":compose:ui:ui-unit"))
+ implementation("androidx.collection:collection:1.4.2")
}
}
@@ -122,4 +122,7 @@
android {
compileSdk 35
namespace "androidx.compose.foundation.layout"
+ buildTypes.configureEach {
+ consumerProguardFiles("proguard-rules.pro")
+ }
}
diff --git a/compose/foundation/foundation-layout/proguard-rules.pro b/compose/foundation/foundation-layout/proguard-rules.pro
new file mode 100644
index 0000000..d07663a
--- /dev/null
+++ b/compose/foundation/foundation-layout/proguard-rules.pro
@@ -0,0 +1,22 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Keep all the functions created to throw an exception. We don't want these functions to be
+# inlined in any way, which R8 will do by default. The whole point of these functions is to
+# reduce the amount of code generated at the call site.
+-keep,allowshrinking,allowobfuscation class androidx.compose.**.* {
+ static void throw*Exception(...);
+ # For methods returning Nothing
+ static java.lang.Void throw*Exception(...);
+}
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AlignmentLine.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AlignmentLine.kt
index bd52f55..fb9ac8c05 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AlignmentLine.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AlignmentLine.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.layout
+import androidx.compose.foundation.layout.internal.requirePrecondition
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.AlignmentLine
@@ -190,7 +191,7 @@
val inspectorInfo: InspectorInfo.() -> Unit
) : ModifierNodeElement<AlignmentLineOffsetDpNode>() {
init {
- require(
+ requirePrecondition(
(before.value >= 0f || before == Dp.Unspecified) &&
(after.value >= 0f || after == Dp.Unspecified)
) {
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AspectRatio.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AspectRatio.kt
index 514f2ed..a94f9eb 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AspectRatio.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AspectRatio.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.layout
import androidx.annotation.FloatRange
+import androidx.compose.foundation.layout.internal.requirePrecondition
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.IntrinsicMeasurable
@@ -74,7 +75,7 @@
val inspectorInfo: InspectorInfo.() -> Unit
) : ModifierNodeElement<AspectRatioNode>() {
init {
- require(aspectRatio > 0) { "aspectRatio $aspectRatio must be > 0" }
+ requirePrecondition(aspectRatio > 0) { "aspectRatio $aspectRatio must be > 0" }
}
override fun create(): AspectRatioNode {
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
index 70e0789..d6408f2 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
@@ -18,6 +18,7 @@
import androidx.annotation.FloatRange
import androidx.compose.foundation.layout.internal.JvmDefaultWithCompatibility
+import androidx.compose.foundation.layout.internal.requirePrecondition
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
@@ -358,7 +359,7 @@
internal object ColumnScopeInstance : ColumnScope {
@Stable
override fun Modifier.weight(weight: Float, fill: Boolean): Modifier {
- require(weight > 0.0) { "invalid weight $weight; must be greater than zero" }
+ requirePrecondition(weight > 0.0) { "invalid weight; must be greater than zero" }
return this.then(
LayoutWeightElement(
// Coerce Float.POSITIVE_INFINITY to Float.MAX_VALUE to avoid errors
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
index 2504197..0a4280f 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.layout
import androidx.annotation.FloatRange
+import androidx.compose.foundation.layout.internal.requirePrecondition
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
@@ -333,10 +334,9 @@
override val maxHeight: Dp
) : RowScope by RowScopeInstance, ContextualFlowRowScope {
override fun Modifier.fillMaxRowHeight(fraction: Float): Modifier {
- require(fraction >= 0.0) {
- "invalid fraction $fraction; must be greater than " + "or equal to zero"
+ requirePrecondition(fraction >= 0.0f && fraction <= 1.0f) {
+ "invalid fraction $fraction; must be >= 0 and <= 1.0"
}
- require(fraction <= 1.0) { "invalid fraction $fraction; must not be greater " + "than 1.0" }
return this.then(
FillCrossAxisSizeElement(
fraction = fraction,
@@ -353,10 +353,9 @@
override val maxHeightInLine: Dp
) : ColumnScope by ColumnScopeInstance, ContextualFlowColumnScope {
override fun Modifier.fillMaxColumnWidth(fraction: Float): Modifier {
- require(fraction >= 0.0) {
- "invalid fraction $fraction; must be greater than or " + "equal to zero"
+ requirePrecondition(fraction >= 0.0f && fraction <= 1.0f) {
+ "invalid fraction $fraction; must be >= 0 and <= 1.0"
}
- require(fraction <= 1.0) { "invalid fraction $fraction; must not be greater " + "than 1.0" }
return this.then(
FillCrossAxisSizeElement(
fraction = fraction,
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
index 0900cfe..3b4aff7 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
@@ -20,6 +20,7 @@
import androidx.collection.IntIntPair
import androidx.collection.mutableIntListOf
import androidx.collection.mutableIntObjectMapOf
+import androidx.compose.foundation.layout.internal.requirePrecondition
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.collection.MutableVector
@@ -254,10 +255,9 @@
@OptIn(ExperimentalLayoutApi::class)
internal object FlowRowScopeInstance : RowScope by RowScopeInstance, FlowRowScope {
override fun Modifier.fillMaxRowHeight(fraction: Float): Modifier {
- require(fraction >= 0.0) {
- "invalid fraction $fraction; must be greater than " + "or equal to zero"
+ requirePrecondition(fraction >= 0.0f && fraction <= 1.0f) {
+ "invalid fraction $fraction; must be >= 0 and <= 1.0"
}
- require(fraction <= 1.0) { "invalid fraction $fraction; must not be greater " + "than 1.0" }
return this.then(
FillCrossAxisSizeElement(
fraction = fraction,
@@ -285,10 +285,9 @@
@OptIn(ExperimentalLayoutApi::class)
internal object FlowColumnScopeInstance : ColumnScope by ColumnScopeInstance, FlowColumnScope {
override fun Modifier.fillMaxColumnWidth(fraction: Float): Modifier {
- require(fraction >= 0.0) {
- "invalid fraction $fraction; must be greater than or " + "equal to zero"
+ requirePrecondition(fraction >= 0.0f && fraction <= 1.0f) {
+ "invalid fraction $fraction; must be >= 0 and <= 1.0"
}
- require(fraction <= 1.0) { "invalid fraction $fraction; must not be greater " + "than 1.0" }
return this.then(
FillCrossAxisSizeElement(
fraction = fraction,
@@ -1457,7 +1456,7 @@
var totalCrossAxisSize = crossAxisTotalSize
// cross axis arrangement
if (isHorizontal) {
- with(requireNotNull(verticalArrangement) { "null verticalArrangement" }) {
+ with(verticalArrangement) {
val totalCrossAxisSpacing = spacing.roundToPx() * (items.size - 1)
totalCrossAxisSize += totalCrossAxisSpacing
totalCrossAxisSize =
@@ -1465,7 +1464,7 @@
arrange(totalCrossAxisSize, crossAxisSizes, outPosition)
}
} else {
- with(requireNotNull(horizontalArrangement) { "null horizontalArrangement" }) {
+ with(horizontalArrangement) {
val totalCrossAxisSpacing = spacing.roundToPx() * (items.size - 1)
totalCrossAxisSize += totalCrossAxisSpacing
totalCrossAxisSize =
@@ -1477,8 +1476,8 @@
val finalMainAxisTotalSize =
mainAxisTotalSize.coerceIn(constraints.mainAxisMin, constraints.mainAxisMax)
- var layoutWidth: Int
- var layoutHeight: Int
+ val layoutWidth: Int
+ val layoutHeight: Int
if (isHorizontal) {
layoutWidth = finalMainAxisTotalSize
layoutHeight = totalCrossAxisSize
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt
index 90c74eb..24ac686 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.layout
import androidx.compose.foundation.layout.PaddingValues.Absolute
+import androidx.compose.foundation.layout.internal.requirePrecondition
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
@@ -202,10 +203,10 @@
) : PaddingValues {
init {
- require(left.value >= 0) { "Left padding must be non-negative" }
- require(top.value >= 0) { "Top padding must be non-negative" }
- require(right.value >= 0) { "Right padding must be non-negative" }
- require(bottom.value >= 0) { "Bottom padding must be non-negative" }
+ requirePrecondition(left.value >= 0) { "Left padding must be non-negative" }
+ requirePrecondition(top.value >= 0) { "Top padding must be non-negative" }
+ requirePrecondition(right.value >= 0) { "Right padding must be non-negative" }
+ requirePrecondition(bottom.value >= 0) { "Bottom padding must be non-negative" }
}
override fun calculateLeftPadding(layoutDirection: LayoutDirection) = left
@@ -290,10 +291,10 @@
) : PaddingValues {
init {
- require(start.value >= 0) { "Start padding must be non-negative" }
- require(top.value >= 0) { "Top padding must be non-negative" }
- require(end.value >= 0) { "End padding must be non-negative" }
- require(bottom.value >= 0) { "Bottom padding must be non-negative" }
+ requirePrecondition(start.value >= 0) { "Start padding must be non-negative" }
+ requirePrecondition(top.value >= 0) { "Top padding must be non-negative" }
+ requirePrecondition(end.value >= 0) { "End padding must be non-negative" }
+ requirePrecondition(bottom.value >= 0) { "Bottom padding must be non-negative" }
}
override fun calculateLeftPadding(layoutDirection: LayoutDirection) =
@@ -330,7 +331,7 @@
) : ModifierNodeElement<PaddingNode>() {
init {
- require(
+ requirePrecondition(
(start.value >= 0f || start == Dp.Unspecified) &&
(top.value >= 0f || top == Dp.Unspecified) &&
(end.value >= 0f || end == Dp.Unspecified) &&
@@ -435,7 +436,7 @@
measurable: Measurable,
constraints: Constraints
): MeasureResult {
- require(
+ requirePrecondition(
paddingValues.calculateLeftPadding(layoutDirection) >= 0.dp &&
paddingValues.calculateTopPadding() >= 0.dp &&
paddingValues.calculateRightPadding(layoutDirection) >= 0.dp &&
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
index b2e98bc..e3ce313 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
@@ -18,6 +18,7 @@
import androidx.annotation.FloatRange
import androidx.compose.foundation.layout.internal.JvmDefaultWithCompatibility
+import androidx.compose.foundation.layout.internal.requirePrecondition
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
@@ -380,7 +381,7 @@
internal object RowScopeInstance : RowScope {
@Stable
override fun Modifier.weight(weight: Float, fill: Boolean): Modifier {
- require(weight > 0.0) { "invalid weight $weight; must be greater than zero" }
+ requirePrecondition(weight > 0.0) { "invalid weight; must be greater than zero" }
return this.then(
LayoutWeightElement(
// Coerce Float.POSITIVE_INFINITY to Float.MAX_VALUE to avoid errors
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurePolicy.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurePolicy.kt
index 41febd5..8482560 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurePolicy.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurePolicy.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.layout
+import androidx.compose.foundation.layout.internal.checkPrecondition
import androidx.compose.ui.layout.AlignmentLine
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
@@ -218,7 +219,7 @@
parentData?.flowLayoutData?.let {
(it.fillCrossAxisFraction * crossAxisMax).fastRoundToInt()
}
- check(weight > 0) { "All weights <= 0 should have placeables" }
+ checkPrecondition(weight > 0) { "All weights <= 0 should have placeables" }
// After the weightUnitSpace rounding, the total space going to be occupied
// can be smaller or larger than remainingToTarget. Here we distribute the
// loss or gain remainder evenly to the first children.
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/internal/InlineClassHelper.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/internal/InlineClassHelper.kt
new file mode 100644
index 0000000..844d5df
--- /dev/null
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/internal/InlineClassHelper.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.layout.internal
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+// This function exists so we do *not* inline the throw. It keeps
+// the call site much smaller and since it's the slow path anyway,
+// we don't mind the extra function call
+internal fun throwIllegalStateException(message: String) {
+ throw IllegalStateException(message)
+}
+
+internal fun throwIllegalStateExceptionForNullCheck(message: String): Nothing {
+ throw IllegalStateException(message)
+}
+
+internal fun throwIllegalArgumentException(message: String) {
+ throw IllegalArgumentException(message)
+}
+
+internal fun throwIndexOutOfBoundsException(message: String) {
+ throw IndexOutOfBoundsException(message)
+}
+
+// Like Kotlin's check() but without the .toString() call and
+// a non-inline throw
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun checkPrecondition(value: Boolean, lazyMessage: () -> String) {
+ contract { returns() implies value }
+ if (!value) {
+ throwIllegalStateException(lazyMessage())
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE", "BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun checkPrecondition(value: Boolean) {
+ contract { returns() implies value }
+ if (!value) {
+ throwIllegalStateException("Check failed.")
+ }
+}
+
+// Like Kotlin's checkNotNull() but without the .toString() call and
+// a non-inline throw
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T : Any> checkPreconditionNotNull(value: T?, lazyMessage: () -> String): T {
+ contract { returns() implies (value != null) }
+
+ if (value == null) {
+ throwIllegalStateExceptionForNullCheck(lazyMessage())
+ }
+
+ return value
+}
+
+// Like Kotlin's checkNotNull() but with a non-inline throw
+@Suppress("NOTHING_TO_INLINE", "BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T : Any> checkPreconditionNotNull(value: T?): T {
+ contract { returns() implies (value != null) }
+
+ if (value == null) {
+ throwIllegalStateExceptionForNullCheck("Required value was null.")
+ }
+
+ return value
+}
+
+// Like Kotlin's require() but without the .toString() call
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class) // same opt-in as using Kotlin's require()
+internal inline fun requirePrecondition(value: Boolean, lazyMessage: () -> String) {
+ contract { returns() implies value }
+ if (!value) {
+ throwIllegalArgumentException(lazyMessage())
+ }
+}
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 79430fc..3c64ee1 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -93,8 +93,10 @@
method public static androidx.compose.foundation.CombinedClickableNode CombinedClickableNode(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, String? onLongClickLabel, kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.IndicationNodeFactory? indicationNodeFactory, boolean enabled, String? onClickLabel, androidx.compose.ui.semantics.Role? role);
method public static androidx.compose.ui.Modifier clickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
method public static androidx.compose.ui.Modifier clickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
- method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
- method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, optional boolean hapticFeedbackEnabled, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method @Deprecated public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, optional boolean hapticFeedbackEnabled, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method @Deprecated public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
}
public final class ClipScrollableContainerKt {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 3254ecd..a3d6ffc 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -93,8 +93,10 @@
method public static androidx.compose.foundation.CombinedClickableNode CombinedClickableNode(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, String? onLongClickLabel, kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.IndicationNodeFactory? indicationNodeFactory, boolean enabled, String? onClickLabel, androidx.compose.ui.semantics.Role? role);
method public static androidx.compose.ui.Modifier clickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
method public static androidx.compose.ui.Modifier clickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
- method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
- method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, optional boolean hapticFeedbackEnabled, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method @Deprecated public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, optional boolean hapticFeedbackEnabled, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method @Deprecated public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
}
public final class ClipScrollableContainerKt {
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index 2122e74..ed11b00 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -41,7 +41,7 @@
commonMain {
dependencies {
implementation(libs.kotlinStdlib)
- api(project(":collection:collection"))
+ api("androidx.collection:collection:1.4.2")
api(project(":compose:animation:animation"))
api(project(":compose:runtime:runtime"))
api(project(":compose:ui:ui"))
@@ -141,6 +141,9 @@
sourceSets.androidTest.assets.srcDirs +=
project.rootDir.absolutePath + "/../../golden/compose/foundation/foundation"
namespace "androidx.compose.foundation"
+ buildTypes.configureEach {
+ consumerProguardFiles("proguard-rules.pro")
+ }
// TODO(b/345531033)
experimentalProperties["android.lint.useK2Uast"] = false
}
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/ScrollableUtils.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/ScrollableUtils.kt
index e603016..2b622b5 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/ScrollableUtils.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/ScrollableUtils.kt
@@ -17,10 +17,12 @@
package androidx.compose.foundation
import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.util.VelocityTracker
+import androidx.compose.ui.input.pointer.util.VelocityTrackerAddPointsFix
import androidx.compose.ui.platform.AbstractComposeView
import androidx.compose.ui.util.fastForEach
import androidx.test.espresso.Espresso
@@ -35,11 +37,16 @@
// Very low tolerance on the difference
internal val VelocityTrackerCalculationThreshold = 1
+@OptIn(ExperimentalComposeUiApi::class)
internal suspend fun savePointerInputEvents(
tracker: VelocityTracker,
pointerInputScope: PointerInputScope
) {
- savePointerInputEventsWithFix(tracker, pointerInputScope)
+ if (VelocityTrackerAddPointsFix) {
+ savePointerInputEventsWithFix(tracker, pointerInputScope)
+ } else {
+ savePointerInputEventsLegacy(tracker, pointerInputScope)
+ }
}
internal suspend fun savePointerInputEventsWithFix(
diff --git a/compose/foundation/foundation/proguard-rules.pro b/compose/foundation/foundation/proguard-rules.pro
new file mode 100644
index 0000000..d07663a
--- /dev/null
+++ b/compose/foundation/foundation/proguard-rules.pro
@@ -0,0 +1,22 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Keep all the functions created to throw an exception. We don't want these functions to be
+# inlined in any way, which R8 will do by default. The whole point of these functions is to
+# reduce the amount of code generated at the call site.
+-keep,allowshrinking,allowobfuscation class androidx.compose.**.* {
+ static void throw*Exception(...);
+ # For methods returning Nothing
+ static java.lang.Void throw*Exception(...);
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableParameterizedKeyInputTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableParameterizedKeyInputTest.kt
index d5f2f0c..65b960f 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableParameterizedKeyInputTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableParameterizedKeyInputTest.kt
@@ -23,6 +23,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReusableContent
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -32,10 +33,13 @@
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusTarget
+import androidx.compose.ui.hapticfeedback.HapticFeedback
+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.InputMode.Companion.Keyboard
import androidx.compose.ui.input.InputModeManager
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.ExperimentalTestApi
@@ -45,6 +49,7 @@
import androidx.compose.ui.test.performKeyInput
import androidx.compose.ui.test.pressKey
import androidx.compose.ui.unit.dp
+import androidx.test.filters.LargeTest
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -232,6 +237,57 @@
}
}
+ @OptIn(ExperimentalTestApi::class)
+ @Test
+ @LargeTest
+ fun longClickWithKey_doesNotTriggerHapticFeedback() {
+ var clickCounter = 0
+ var longClickCounter = 0
+ val focusRequester = FocusRequester()
+ lateinit var inputModeManager: InputModeManager
+ val performedHaptics = mutableListOf<HapticFeedbackType>()
+
+ val hapticFeedback: HapticFeedback =
+ object : HapticFeedback {
+ override fun performHapticFeedback(hapticFeedbackType: HapticFeedbackType) {
+ performedHaptics += hapticFeedbackType
+ }
+ }
+ rule.setContent {
+ inputModeManager = LocalInputModeManager.current
+ CompositionLocalProvider(LocalHapticFeedback provides hapticFeedback) {
+ BasicText(
+ "ClickableText",
+ modifier =
+ Modifier.testTag("myClickable")
+ .focusRequester(focusRequester)
+ .combinedClickable(
+ onLongClick = { ++longClickCounter },
+ onClick = { ++clickCounter },
+ hapticFeedbackEnabled = true
+ )
+ )
+ }
+ }
+ rule.runOnIdle {
+ inputModeManager.requestInputMode(Keyboard)
+ focusRequester.requestFocus()
+ }
+
+ rule.onNodeWithTag("myClickable").performKeyInput {
+ assertThat(inputModeManager.inputMode).isEqualTo(Keyboard)
+ // The press duration is 100ms longer than the minimum required for a long press.
+ val durationMillis: Long = viewConfiguration.longPressTimeoutMillis + 100
+ pressKey(key, durationMillis)
+ }
+
+ rule.runOnIdle {
+ assertThat(longClickCounter).isEqualTo(1)
+ assertThat(clickCounter).isEqualTo(0)
+ assertThat(performedHaptics).isEmpty()
+ }
+ }
+
@Test
@OptIn(ExperimentalTestApi::class)
fun longClickWithKey_notInvokedIfFocusIsLostWhilePressed() {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
index 44c3864..165c8c5 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
@@ -50,6 +50,8 @@
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.hapticfeedback.HapticFeedback
+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.InputMode
import androidx.compose.ui.input.InputMode.Companion.Keyboard
import androidx.compose.ui.input.InputMode.Companion.Touch
@@ -57,6 +59,7 @@
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.platform.InspectableValue
import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
import androidx.compose.ui.platform.testTag
@@ -318,6 +321,107 @@
}
@Test
+ @LargeTest
+ fun longClick_hapticFeedbackEnabled() {
+ var counter = 0
+ val onClick: () -> Unit = { ++counter }
+ val performedHaptics = mutableListOf<HapticFeedbackType>()
+
+ val hapticFeedback: HapticFeedback =
+ object : HapticFeedback {
+ override fun performHapticFeedback(hapticFeedbackType: HapticFeedbackType) {
+ performedHaptics += hapticFeedbackType
+ }
+ }
+
+ rule.setContent {
+ CompositionLocalProvider(LocalHapticFeedback provides hapticFeedback) {
+ Box {
+ BasicText(
+ "ClickableText",
+ modifier =
+ Modifier.testTag("myClickable").combinedClickable(
+ onLongClick = onClick,
+ hapticFeedbackEnabled = true
+ ) {}
+ )
+ }
+ }
+ }
+
+ rule.onNodeWithTag("myClickable").performTouchInput { down(center) }
+
+ // Advance a small amount of time
+ rule.mainClock.advanceTimeBy(100)
+
+ rule.onNodeWithTag("myClickable").performTouchInput { up() }
+
+ // Releasing the press before the long click timeout shouldn't trigger haptic feedback
+ rule.runOnIdle { assertThat(counter).isEqualTo(0) }
+ rule.runOnIdle { assertThat(performedHaptics).isEmpty() }
+
+ rule.onNodeWithTag("myClickable").performTouchInput { down(center) }
+
+ // Advance past the long press timeout
+ rule.mainClock.advanceTimeBy(1000)
+
+ // Long press haptic feedback should be invoked
+ rule.runOnIdle { assertThat(counter).isEqualTo(1) }
+ rule.runOnIdle {
+ assertThat(performedHaptics).containsExactly(HapticFeedbackType.LongPress)
+ }
+ }
+
+ @Test
+ @LargeTest
+ fun longClick_hapticFeedbackDisabled() {
+ var counter = 0
+ val onClick: () -> Unit = { ++counter }
+ val performedHaptics = mutableListOf<HapticFeedbackType>()
+
+ val hapticFeedback: HapticFeedback =
+ object : HapticFeedback {
+ override fun performHapticFeedback(hapticFeedbackType: HapticFeedbackType) {
+ performedHaptics += hapticFeedbackType
+ }
+ }
+
+ rule.setContent {
+ CompositionLocalProvider(LocalHapticFeedback provides hapticFeedback) {
+ Box {
+ BasicText(
+ "ClickableText",
+ modifier =
+ Modifier.testTag("myClickable").combinedClickable(
+ onLongClick = onClick,
+ hapticFeedbackEnabled = false
+ ) {}
+ )
+ }
+ }
+ }
+
+ rule.onNodeWithTag("myClickable").performTouchInput { down(center) }
+
+ // Advance a small amount of time
+ rule.mainClock.advanceTimeBy(100)
+
+ rule.onNodeWithTag("myClickable").performTouchInput { up() }
+
+ rule.runOnIdle { assertThat(counter).isEqualTo(0) }
+ rule.runOnIdle { assertThat(performedHaptics).isEmpty() }
+
+ rule.onNodeWithTag("myClickable").performTouchInput { down(center) }
+
+ // Advance past the long press timeout
+ rule.mainClock.advanceTimeBy(1000)
+
+ // Long press should be invoked, without any haptics
+ rule.runOnIdle { assertThat(counter).isEqualTo(1) }
+ rule.runOnIdle { assertThat(performedHaptics).isEmpty() }
+ }
+
+ @Test
@OptIn(ExperimentalTestApi::class)
fun longClickWithEnterKeyThenDPadCenter_triggersListenerTwice() {
var clickCounter = 0
@@ -1808,7 +1912,8 @@
"onClick",
"onDoubleClick",
"onLongClick",
- "onLongClickLabel"
+ "onLongClickLabel",
+ "hapticFeedbackEnabled"
)
}
}
@@ -1836,7 +1941,8 @@
"onLongClick",
"onLongClickLabel",
"indicationNodeFactory",
- "interactionSource"
+ "interactionSource",
+ "hapticFeedbackEnabled"
)
}
}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index c4d5f68..1f21478 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -60,6 +60,7 @@
import androidx.compose.testutils.assertModifierIsPure
import androidx.compose.testutils.first
import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.MotionDurationScale
import androidx.compose.ui.focus.FocusDirection
@@ -79,6 +80,7 @@
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.util.VelocityTracker
+import androidx.compose.ui.input.pointer.util.VelocityTrackerAddPointsFix
import androidx.compose.ui.materialize
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.TraversableNode
@@ -3065,11 +3067,16 @@
// Very low tolerance on the difference
internal val VelocityTrackerCalculationThreshold = 1
+@OptIn(ExperimentalComposeUiApi::class)
internal suspend fun savePointerInputEvents(
tracker: VelocityTracker,
pointerInputScope: PointerInputScope
) {
- savePointerInputEventsWithFix(tracker, pointerInputScope)
+ if (VelocityTrackerAddPointsFix) {
+ savePointerInputEventsWithFix(tracker, pointerInputScope)
+ } else {
+ savePointerInputEventsLegacy(tracker, pointerInputScope)
+ }
}
internal suspend fun savePointerInputEventsWithFix(
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
index db9f9743..c9fad46 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
@@ -19,6 +19,7 @@
import android.view.accessibility.AccessibilityNodeProvider
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.focusable
+import androidx.compose.foundation.internal.checkPreconditionNotNull
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
@@ -49,7 +50,7 @@
private val accessibilityNodeProvider: AccessibilityNodeProvider
get() =
- checkNotNull(composeView) { "composeView not initialized." }
+ checkPreconditionNotNull(composeView) { "composeView not initialized." }
.let { composeView ->
ViewCompat.getAccessibilityDelegate(composeView)!!.getAccessibilityNodeProvider(
composeView
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt
index 89077c7..9cad429 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt
@@ -19,6 +19,7 @@
import android.os.Looper
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
+import androidx.compose.foundation.internal.checkPreconditionNotNull
import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.platform.InterceptPlatformTextInput
@@ -149,7 +150,7 @@
fun withInputConnection(block: InputConnection.() -> Unit) {
runOnIdle {
val inputConnection =
- checkNotNull(inputConnection) {
+ checkPreconditionNotNull(inputConnection) {
"Tried to read inputConnection while no session was active"
}
block(inputConnection)
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuState.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuState.android.kt
index 931c700..6727ef5 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuState.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuState.android.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.contextmenu
import androidx.compose.foundation.contextmenu.ContextMenuState.Status
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -49,7 +50,7 @@
val offset: Offset
) : Status() {
init {
- check(offset.isSpecified) { UNSPECIFIED_OFFSET_ERROR_MESSAGE }
+ checkPrecondition(offset.isSpecified) { UNSPECIFIED_OFFSET_ERROR_MESSAGE }
}
override fun toString(): String = "Open(offset=$offset)"
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuUi.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuUi.android.kt
index 595b7b0..e4a3f61 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuUi.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuUi.android.kt
@@ -25,6 +25,7 @@
import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -280,7 +281,7 @@
) {
composables += { colors ->
val resolvedLabel = label()
- check(resolvedLabel.isNotBlank()) { "Label must not be blank" }
+ checkPrecondition(resolvedLabel.isNotBlank()) { "Label must not be blank" }
ContextMenuItem(
modifier = modifier,
label = resolvedLabel,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/CheckScrollableContainerConstraints.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/CheckScrollableContainerConstraints.kt
index 9d7b797..b623830 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/CheckScrollableContainerConstraints.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/CheckScrollableContainerConstraints.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.ui.unit.Constraints
/**
@@ -28,7 +29,7 @@
*/
fun checkScrollableContainerConstraints(constraints: Constraints, orientation: Orientation) {
if (orientation == Orientation.Vertical) {
- check(constraints.maxHeight != Constraints.Infinity) {
+ checkPrecondition(constraints.maxHeight != Constraints.Infinity) {
"Vertically scrollable component was measured with an infinity maximum height " +
"constraints, which is disallowed. One of the common reasons is nesting layouts " +
"like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a " +
@@ -40,7 +41,7 @@
"hierarchy above the scrolling container."
}
} else {
- check(constraints.maxWidth != Constraints.Infinity) {
+ checkPrecondition(constraints.maxWidth != Constraints.Infinity) {
"Horizontally scrollable component was measured with an infinity maximum width " +
"constraints, which is disallowed. One of the common reasons is nesting layouts " +
"like LazyRow and Row(Modifier.horizontalScroll()). If you want to add a " +
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
index 5a2dde7..ee11b6e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -29,6 +29,8 @@
import androidx.compose.ui.composed
import androidx.compose.ui.focus.Focusability
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.hapticfeedback.HapticFeedback
+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyInputModifierNode
import androidx.compose.ui.input.key.key
@@ -48,6 +50,7 @@
import androidx.compose.ui.node.invalidateSemantics
import androidx.compose.ui.node.traverseAncestors
import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.semantics.Role
@@ -228,6 +231,7 @@
* @param onLongClickLabel semantic / accessibility label for the [onLongClick] action
* @param onLongClick will be called when user long presses on the element
* @param onDoubleClick will be called when user double clicks on the element
+ * @param hapticFeedbackEnabled whether to use the default [HapticFeedback] behavior
* @param onClick will be called when user clicks on the element
*/
fun Modifier.combinedClickable(
@@ -237,6 +241,56 @@
onLongClickLabel: String? = null,
onLongClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
+ hapticFeedbackEnabled: Boolean = true,
+ onClick: () -> Unit
+) =
+ composed(
+ inspectorInfo =
+ debugInspectorInfo {
+ name = "combinedClickable"
+ properties["enabled"] = enabled
+ properties["onClickLabel"] = onClickLabel
+ properties["role"] = role
+ properties["onClick"] = onClick
+ properties["onDoubleClick"] = onDoubleClick
+ properties["onLongClick"] = onLongClick
+ properties["onLongClickLabel"] = onLongClickLabel
+ properties["hapticFeedbackEnabled"] = hapticFeedbackEnabled
+ }
+ ) {
+ val localIndication = LocalIndication.current
+ val interactionSource =
+ if (localIndication is IndicationNodeFactory) {
+ // We can fast path here as it will be created inside clickable lazily
+ null
+ } else {
+ // We need an interaction source to pass between the indication modifier and
+ // clickable, so
+ // by creating here we avoid another composed down the line
+ remember { MutableInteractionSource() }
+ }
+ Modifier.combinedClickable(
+ enabled = enabled,
+ onClickLabel = onClickLabel,
+ onLongClickLabel = onLongClickLabel,
+ onLongClick = onLongClick,
+ onDoubleClick = onDoubleClick,
+ onClick = onClick,
+ role = role,
+ indication = localIndication,
+ interactionSource = interactionSource,
+ hapticFeedbackEnabled = hapticFeedbackEnabled
+ )
+ }
+
+@Deprecated(message = "Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
+fun Modifier.combinedClickable(
+ enabled: Boolean = true,
+ onClickLabel: String? = null,
+ role: Role? = null,
+ onLongClickLabel: String? = null,
+ onLongClick: (() -> Unit)? = null,
+ onDoubleClick: (() -> Unit)? = null,
onClick: () -> Unit
) =
composed(
@@ -272,7 +326,8 @@
onClick = onClick,
role = role,
indication = localIndication,
- interactionSource = interactionSource
+ interactionSource = interactionSource,
+ hapticFeedbackEnabled = true
)
}
@@ -322,6 +377,7 @@
* @param onLongClickLabel semantic / accessibility label for the [onLongClick] action
* @param onLongClick will be called when user long presses on the element
* @param onDoubleClick will be called when user double clicks on the element
+ * @param hapticFeedbackEnabled whether to use the default [HapticFeedback] behavior
* @param onClick will be called when user clicks on the element
*/
fun Modifier.combinedClickable(
@@ -333,6 +389,37 @@
onLongClickLabel: String? = null,
onLongClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
+ hapticFeedbackEnabled: Boolean = true,
+ onClick: () -> Unit
+) =
+ clickableWithIndicationIfNeeded(
+ interactionSource = interactionSource,
+ indication = indication
+ ) { intSource, indicationNodeFactory ->
+ CombinedClickableElement(
+ interactionSource = intSource,
+ indicationNodeFactory = indicationNodeFactory,
+ enabled = enabled,
+ onClickLabel = onClickLabel,
+ role = role,
+ onClick = onClick,
+ onLongClickLabel = onLongClickLabel,
+ onLongClick = onLongClick,
+ onDoubleClick = onDoubleClick,
+ hapticFeedbackEnabled = hapticFeedbackEnabled
+ )
+ }
+
+@Deprecated(message = "Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
+fun Modifier.combinedClickable(
+ interactionSource: MutableInteractionSource?,
+ indication: Indication?,
+ enabled: Boolean = true,
+ onClickLabel: String? = null,
+ role: Role? = null,
+ onLongClickLabel: String? = null,
+ onLongClick: (() -> Unit)? = null,
+ onDoubleClick: (() -> Unit)? = null,
onClick: () -> Unit
) =
clickableWithIndicationIfNeeded(
@@ -348,7 +435,8 @@
onClick = onClick,
onLongClickLabel = onLongClickLabel,
onLongClick = onLongClick,
- onDoubleClick = onDoubleClick
+ onDoubleClick = onDoubleClick,
+ hapticFeedbackEnabled = true
)
}
@@ -481,7 +569,8 @@
private val onClick: () -> Unit,
private val onLongClickLabel: String?,
private val onLongClick: (() -> Unit)?,
- private val onDoubleClick: (() -> Unit)?
+ private val onDoubleClick: (() -> Unit)?,
+ private val hapticFeedbackEnabled: Boolean,
) : ModifierNodeElement<CombinedClickableNodeImpl>() {
override fun create() =
CombinedClickableNodeImpl(
@@ -489,6 +578,7 @@
onLongClickLabel,
onLongClick,
onDoubleClick,
+ hapticFeedbackEnabled,
interactionSource,
indicationNodeFactory,
enabled,
@@ -497,6 +587,7 @@
)
override fun update(node: CombinedClickableNodeImpl) {
+ node.hapticFeedbackEnabled = hapticFeedbackEnabled
node.update(
onClick,
onLongClickLabel,
@@ -521,6 +612,7 @@
properties["onDoubleClick"] = onDoubleClick
properties["onLongClick"] = onLongClick
properties["onLongClickLabel"] = onLongClickLabel
+ properties["hapticFeedbackEnabled"] = hapticFeedbackEnabled
}
override fun equals(other: Any?): Boolean {
@@ -539,6 +631,7 @@
if (onLongClickLabel != other.onLongClickLabel) return false
if (onLongClick !== other.onLongClick) return false
if (onDoubleClick !== other.onDoubleClick) return false
+ if (hapticFeedbackEnabled != other.hapticFeedbackEnabled) return false
return true
}
@@ -553,6 +646,7 @@
result = 31 * result + (onLongClickLabel?.hashCode() ?: 0)
result = 31 * result + (onLongClick?.hashCode() ?: 0)
result = 31 * result + (onDoubleClick?.hashCode() ?: 0)
+ result = 31 * result + hapticFeedbackEnabled.hashCode()
return result
}
}
@@ -645,6 +739,7 @@
onLongClickLabel,
onLongClick,
onDoubleClick,
+ hapticFeedbackEnabled = true,
interactionSource,
indicationNodeFactory,
enabled,
@@ -696,6 +791,7 @@
private var onLongClickLabel: String?,
private var onLongClick: (() -> Unit)?,
private var onDoubleClick: (() -> Unit)?,
+ var hapticFeedbackEnabled: Boolean,
interactionSource: MutableInteractionSource?,
indicationNodeFactory: IndicationNodeFactory?,
enabled: Boolean,
@@ -722,7 +818,13 @@
} else null,
onLongPress =
if (enabled && onLongClick != null) {
- { onLongClick?.invoke() }
+ {
+ onLongClick?.invoke()
+ if (hapticFeedbackEnabled) {
+ currentValueOf(LocalHapticFeedback)
+ .performHapticFeedback(HapticFeedbackType.LongPress)
+ }
+ }
} else null,
onPress = { offset ->
if (enabled) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
index 40708d7..fa72269 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
@@ -34,6 +34,8 @@
import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
import androidx.compose.foundation.gestures.snapping.snapFlingBehavior
import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
@@ -855,7 +857,7 @@
* @see offset
*/
fun requireOffset(): Float {
- check(!offset.isNaN()) {
+ checkPrecondition(!offset.isNaN()) {
"The offset was read before being initialized. Did you access the offset in a phase " +
"before layout, like effects or composition?"
}
@@ -989,7 +991,7 @@
*/
@Deprecated(SettleWithVelocityDeprecated, level = DeprecationLevel.WARNING)
suspend fun settle(velocity: Float): Float {
- require(usePreModifierChangeBehavior) {
+ requirePrecondition(usePreModifierChangeBehavior) {
"AnchoredDraggableState was configured through " +
"a constructor without providing positional and velocity threshold. This " +
"overload of settle has been deprecated. Please refer to " +
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue.kt
index fb8247b..3e41151 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.gestures
import androidx.compose.foundation.gestures.ContentInViewNode.Request
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.runtime.collection.mutableVectorOf
import androidx.compose.ui.geometry.Rect
import kotlin.contracts.ExperimentalContracts
@@ -130,6 +131,6 @@
// cancelled, so we need to make a copy of the list before iterating to avoid concurrent
// mutation.
requests.map { it.continuation }.forEach { it.cancel(cause) }
- check(requests.isEmpty()) { "uncancelled requests present" }
+ checkPrecondition(requests.isEmpty()) { "uncancelled requests present" }
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
index e1cd608..c8c9f71 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
@@ -20,6 +20,7 @@
import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.gestures.Orientation.Horizontal
import androidx.compose.foundation.gestures.Orientation.Vertical
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.BringIntoViewResponder
import androidx.compose.ui.Modifier
@@ -111,7 +112,7 @@
private var isAnimationRunning = false
override fun calculateRectForParent(localRect: Rect): Rect {
- check(viewportSize != IntSize.Zero) {
+ checkPrecondition(viewportSize != IntSize.Zero) {
"Expected BringIntoViewRequester to not be used before parents are placed."
}
// size will only be zero before the initial measurement.
@@ -187,7 +188,9 @@
private fun launchAnimation() {
val bringIntoViewSpec = requireBringIntoViewSpec()
- check(!isAnimationRunning) { "launchAnimation called when previous animation was running" }
+ checkPrecondition(!isAnimationRunning) {
+ "launchAnimation called when previous animation was running"
+ }
if (DEBUG) println("[$TAG] launchAnimation")
val animationState = UpdatableAnimationState(BringIntoViewSpec.DefaultScrollAnimationSpec)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformableState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformableState.kt
index aab1f19..dd941ee 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformableState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformableState.kt
@@ -33,6 +33,7 @@
import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.MutatorMutex
import androidx.compose.foundation.internal.JvmDefaultWithCompatibility
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -136,7 +137,7 @@
zoomFactor: Float,
animationSpec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow)
) {
- require(zoomFactor > 0) { "zoom value should be greater than 0" }
+ requirePrecondition(zoomFactor > 0) { "zoom value should be greater than 0" }
var previous = 1f
transform {
AnimationState(initialValue = previous).animateTo(zoomFactor, animationSpec) {
@@ -214,7 +215,7 @@
offsetAnimationSpec: AnimationSpec<Offset> = SpringSpec(stiffness = Spring.StiffnessLow),
rotationAnimationSpec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow)
) {
- require(zoomFactor > 0) { "zoom value should be greater than 0" }
+ requirePrecondition(zoomFactor > 0) { "zoom value should be greater than 0" }
var previousState = AnimationData(zoom = 1f, offset = Offset.Zero, degrees = 0f)
val targetState = AnimationData(zoomFactor, offset, degrees)
val animationSpec =
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/UpdatableAnimationState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/UpdatableAnimationState.kt
index e6890fd..83dd7fd 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/UpdatableAnimationState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/UpdatableAnimationState.kt
@@ -21,6 +21,7 @@
import androidx.compose.animation.core.AnimationState
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.VectorConverter
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.MotionDurationScale
import kotlin.contracts.ExperimentalContracts
@@ -88,7 +89,7 @@
afterFrame: () -> Unit,
) {
contract { callsInPlace(beforeFrame) }
- check(!isRunning) { "animateToZero called while previous animation is running" }
+ checkPrecondition(!isRunning) { "animateToZero called while previous animation is running" }
val durationScale = coroutineContext[MotionDurationScale]?.scaleFactor ?: 1f
isRunning = true
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt
index 1f7188c..f1b6803 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.gestures.snapping
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.foundation.pager.PagerDebugConfig
import androidx.compose.foundation.pager.PagerLayoutInfo
import androidx.compose.foundation.pager.PagerSnapDistance
@@ -48,7 +49,7 @@
val finalDistance =
calculateFinalSnappingBound(velocity, lowerBoundOffset, upperBoundOffset)
- check(
+ checkPrecondition(
finalDistance == lowerBoundOffset ||
finalDistance == upperBoundOffset ||
finalDistance == 0.0f
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt
index e4e0ac9..60d9705 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt
@@ -33,6 +33,7 @@
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.foundation.gestures.TargetedFlingBehavior
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalDensity
@@ -118,7 +119,7 @@
val initialOffset =
snapLayoutInfoProvider.calculateApproachOffset(initialVelocity, decayOffset)
- check(!initialOffset.isNaN()) {
+ checkPrecondition(!initialOffset.isNaN()) {
"calculateApproachOffset returned NaN. Please use a valid value."
}
@@ -136,7 +137,7 @@
val finalSnapOffset =
snapLayoutInfoProvider.calculateSnapOffset(animationState.velocity)
- check(!finalSnapOffset.isNaN()) {
+ checkPrecondition(!finalSnapOffset.isNaN()) {
"calculateSnapOffset returned NaN. Please use a valid value."
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/internal/InlineClassHelper.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/internal/InlineClassHelper.kt
new file mode 100644
index 0000000..4af862a
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/internal/InlineClassHelper.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.internal
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+// This function exists so we do *not* inline the throw. It keeps
+// the call site much smaller and since it's the slow path anyway,
+// we don't mind the extra function call
+internal fun throwIllegalStateException(message: String) {
+ throw IllegalStateException(message)
+}
+
+internal fun throwIllegalStateExceptionForNullCheck(message: String): Nothing {
+ throw IllegalStateException(message)
+}
+
+internal fun throwIllegalArgumentException(message: String) {
+ throw IllegalArgumentException(message)
+}
+
+internal fun throwIllegalArgumentExceptionForNullCheck(message: String): Nothing {
+ throw IllegalArgumentException(message)
+}
+
+internal fun throwIndexOutOfBoundsException(message: String) {
+ throw IndexOutOfBoundsException(message)
+}
+
+// Like Kotlin's check() but without the .toString() call and
+// a non-inline throw
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun checkPrecondition(value: Boolean, lazyMessage: () -> String) {
+ contract { returns() implies value }
+ if (!value) {
+ throwIllegalStateException(lazyMessage())
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE", "BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun checkPrecondition(value: Boolean) {
+ contract { returns() implies value }
+ if (!value) {
+ throwIllegalStateException("Check failed.")
+ }
+}
+
+// Like Kotlin's checkNotNull() but without the .toString() call and
+// a non-inline throw
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T : Any> checkPreconditionNotNull(value: T?, lazyMessage: () -> String): T {
+ contract { returns() implies (value != null) }
+
+ if (value == null) {
+ throwIllegalStateExceptionForNullCheck(lazyMessage())
+ }
+
+ return value
+}
+
+// Like Kotlin's checkNotNull() but with a non-inline throw
+@Suppress("NOTHING_TO_INLINE", "BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T : Any> checkPreconditionNotNull(value: T?): T {
+ contract { returns() implies (value != null) }
+
+ if (value == null) {
+ throwIllegalStateExceptionForNullCheck("Required value was null.")
+ }
+
+ return value
+}
+
+// Like Kotlin's require() but without the .toString() call
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class) // same opt-in as using Kotlin's require()
+internal inline fun requirePrecondition(value: Boolean, lazyMessage: () -> String) {
+ contract { returns() implies value }
+ if (!value) {
+ throwIllegalArgumentException(lazyMessage())
+ }
+}
+
+// Like Kotlin's checkNotNull() but without the .toString() call and
+// a non-inline throw
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T : Any> requirePreconditionNotNull(value: T?, lazyMessage: () -> String): T {
+ contract { returns() implies (value != null) }
+
+ if (value == null) {
+ throwIllegalArgumentExceptionForNullCheck(lazyMessage())
+ }
+
+ return value
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
index 5e8c9f5..aecfa7c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
@@ -21,6 +21,7 @@
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.internal.requirePreconditionNotNull
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
@@ -243,12 +244,12 @@
val spaceBetweenItemsDp =
if (isVertical) {
- requireNotNull(verticalArrangement) {
+ requirePreconditionNotNull(verticalArrangement) {
"null verticalArrangement when isVertical == true"
}
.spacing
} else {
- requireNotNull(horizontalArrangement) {
+ requirePreconditionNotNull(horizontalArrangement) {
"null horizontalAlignment when isVertical == false"
}
.spacing
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
index 7fc0988..b5728a0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
@@ -17,6 +17,9 @@
package androidx.compose.foundation.lazy
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
+import androidx.compose.foundation.internal.requirePreconditionNotNull
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimator
import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
@@ -71,8 +74,8 @@
graphicsContext: GraphicsContext,
layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
): LazyListMeasureResult {
- require(beforeContentPadding >= 0) { "invalid beforeContentPadding" }
- require(afterContentPadding >= 0) { "invalid afterContentPadding" }
+ requirePrecondition(beforeContentPadding >= 0) { "invalid beforeContentPadding" }
+ requirePrecondition(afterContentPadding >= 0) { "invalid afterContentPadding" }
if (itemsCount <= 0) {
// empty data set. reset the current scroll and report zero size
var layoutWidth = constraints.minWidth
@@ -278,7 +281,9 @@
} else 0f
// the initial offset for items from visibleItems list
- require(currentFirstItemScrollOffset >= 0) { "negative currentFirstItemScrollOffset" }
+ requirePrecondition(currentFirstItemScrollOffset >= 0) {
+ "negative currentFirstItemScrollOffset"
+ }
val visibleItemsScrollOffset = -currentFirstItemScrollOffset
var firstItem = visibleItems.first()
@@ -583,14 +588,16 @@
val mainAxisLayoutSize = if (isVertical) layoutHeight else layoutWidth
val hasSpareSpace = finalMainAxisOffset < minOf(mainAxisLayoutSize, maxOffset)
if (hasSpareSpace) {
- check(itemsScrollOffset == 0) { "non-zero itemsScrollOffset" }
+ checkPrecondition(itemsScrollOffset == 0) { "non-zero itemsScrollOffset" }
}
val positionedItems =
ArrayList<LazyListMeasuredItem>(items.size + extraItemsBefore.size + extraItemsAfter.size)
if (hasSpareSpace) {
- require(extraItemsBefore.isEmpty() && extraItemsAfter.isEmpty()) { "no extra items" }
+ requirePrecondition(extraItemsBefore.isEmpty() && extraItemsAfter.isEmpty()) {
+ "no extra items"
+ }
val itemsCount = items.size
fun Int.reverseAware() = if (!reverseLayout) this else itemsCount - this - 1
@@ -599,7 +606,7 @@
val offsets = IntArray(itemsCount) { 0 }
if (isVertical) {
with(
- requireNotNull(verticalArrangement) {
+ requirePreconditionNotNull(verticalArrangement) {
"null verticalArrangement when isVertical == true"
}
) {
@@ -607,7 +614,7 @@
}
} else {
with(
- requireNotNull(horizontalArrangement) {
+ requirePreconditionNotNull(horizontalArrangement) {
"null horizontalArrangement when isVertical == false"
}
) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItem.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItem.kt
index 73a509f..bcee59d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItem.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItem.kt
@@ -16,6 +16,8 @@
package androidx.compose.foundation.lazy
+import androidx.compose.foundation.internal.requirePrecondition
+import androidx.compose.foundation.internal.requirePreconditionNotNull
import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimation.Companion.NotInitialized
import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimator
import androidx.compose.foundation.lazy.layout.LazyLayoutMeasuredItem
@@ -128,7 +130,7 @@
val indexInArray = index * 2
if (isVertical) {
placeableOffsets[indexInArray] =
- requireNotNull(horizontalAlignment) {
+ requirePreconditionNotNull(horizontalAlignment) {
"null horizontalAlignment when isVertical == true"
}
.align(placeable.width, layoutWidth, layoutDirection)
@@ -137,7 +139,7 @@
} else {
placeableOffsets[indexInArray] = mainAxisOffset
placeableOffsets[indexInArray + 1] =
- requireNotNull(verticalAlignment) {
+ requirePreconditionNotNull(verticalAlignment) {
"null verticalAlignment when isVertical == false"
}
.align(placeable.height, layoutHeight)
@@ -184,7 +186,7 @@
fun place(scope: Placeable.PlacementScope, isLookingAhead: Boolean) =
with(scope) {
- require(mainAxisLayoutSize != Unset) { "position() should be called first" }
+ requirePrecondition(mainAxisLayoutSize != Unset) { "position() should be called first" }
repeat(placeablesCount) { index ->
val placeable = placeables[index]
val minOffset = minMainAxisOffset - placeable.mainAxisSize
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt
index d8d9515..57fd4ca 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt
@@ -17,6 +17,8 @@
package androidx.compose.foundation.lazy
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.lazy.layout.LazyLayoutNearestRangeState
import androidx.compose.foundation.lazy.layout.findIndexByKey
import androidx.compose.runtime.getValue
@@ -54,7 +56,7 @@
if (hadFirstNotEmptyLayout || measureResult.totalItemsCount > 0) {
hadFirstNotEmptyLayout = true
val scrollOffset = measureResult.firstVisibleItemScrollOffset
- check(scrollOffset >= 0f) { "scrollOffset should be non-negative ($scrollOffset)" }
+ checkPrecondition(scrollOffset >= 0f) { "scrollOffset should be non-negative" }
val firstIndex = measureResult.firstVisibleItem?.index ?: 0
update(firstIndex, scrollOffset)
@@ -62,7 +64,7 @@
}
fun updateScrollOffset(scrollOffset: Int) {
- check(scrollOffset >= 0f) { "scrollOffset should be non-negative ($scrollOffset)" }
+ checkPrecondition(scrollOffset >= 0f) { "scrollOffset should be non-negative" }
this.scrollOffset = scrollOffset
}
@@ -103,7 +105,7 @@
}
private fun update(index: Int, scrollOffset: Int) {
- require(index >= 0f) { "Index should be non-negative ($index)" }
+ requirePrecondition(index >= 0f) { "Index should be non-negative ($index)" }
this.index = index
nearestRangeState.update(index)
this.scrollOffset = scrollOffset
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
index 98a2b18..e008329 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
@@ -31,6 +31,7 @@
import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.foundation.lazy.LazyListState.Companion.Saver
import androidx.compose.foundation.lazy.layout.AwaitFirstLayoutModifier
import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
@@ -400,8 +401,8 @@
if (distance < 0 && !canScrollForward || distance > 0 && !canScrollBackward) {
return 0f
}
- check(abs(scrollToBeConsumed) <= 0.5f) {
- "entered drag with non-zero pending scroll: $scrollToBeConsumed"
+ checkPrecondition(abs(scrollToBeConsumed) <= 0.5f) {
+ "entered drag with non-zero pending scroll"
}
scrollToBeConsumed += distance
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
index 6611091..784f123 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
@@ -21,6 +21,7 @@
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.internal.requirePreconditionNotNull
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
@@ -225,12 +226,12 @@
val spaceBetweenLinesDp =
if (isVertical) {
- requireNotNull(verticalArrangement) {
+ requirePreconditionNotNull(verticalArrangement) {
"null verticalArrangement when isVertical == true"
}
.spacing
} else {
- requireNotNull(horizontalArrangement) {
+ requirePreconditionNotNull(horizontalArrangement) {
"null horizontalArrangement when isVertical == false"
}
.spacing
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt
index dd07ff2..b814ee9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt
@@ -18,6 +18,7 @@
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
@@ -155,7 +156,7 @@
contentPadding,
) {
GridSlotCache { constraints ->
- require(constraints.maxWidth != Constraints.Infinity) {
+ requirePrecondition(constraints.maxWidth != Constraints.Infinity) {
"LazyVerticalGrid's width should be bound by parent."
}
val horizontalPadding =
@@ -189,7 +190,7 @@
contentPadding,
) {
GridSlotCache { constraints ->
- require(constraints.maxHeight != Constraints.Infinity) {
+ requirePrecondition(constraints.maxHeight != Constraints.Infinity) {
"LazyHorizontalGrid's height should be bound by parent."
}
val verticalPadding =
@@ -269,7 +270,7 @@
*/
class Fixed(private val count: Int) : GridCells {
init {
- require(count > 0) { "Provided count $count should be larger than zero" }
+ requirePrecondition(count > 0) { "Provided count should be larger than zero" }
}
override fun Density.calculateCrossAxisCellSizes(
@@ -298,7 +299,7 @@
*/
class Adaptive(private val minSize: Dp) : GridCells {
init {
- require(minSize > 0.dp) { "Provided min size $minSize should be larger than zero." }
+ requirePrecondition(minSize > 0.dp) { "Provided min size should be larger than zero." }
}
override fun Density.calculateCrossAxisCellSizes(
@@ -331,7 +332,7 @@
*/
class FixedSize(private val size: Dp) : GridCells {
init {
- require(size > 0.dp) { "Provided size $size should be larger than zero." }
+ requirePrecondition(size > 0.dp) { "Provided size should be larger than zero." }
}
override fun Density.calculateCrossAxisCellSizes(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
index e533ffb..549b2a7 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
@@ -17,6 +17,9 @@
package androidx.compose.foundation.lazy.grid
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
+import androidx.compose.foundation.internal.requirePreconditionNotNull
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimator
import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
@@ -69,8 +72,8 @@
prefetchInfoRetriever: (line: Int) -> List<Pair<Int, Constraints>>,
layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
): LazyGridMeasureResult {
- require(beforeContentPadding >= 0) { "negative beforeContentPadding" }
- require(afterContentPadding >= 0) { "negative afterContentPadding" }
+ requirePrecondition(beforeContentPadding >= 0) { "negative beforeContentPadding" }
+ requirePrecondition(afterContentPadding >= 0) { "negative afterContentPadding" }
if (itemsCount <= 0) {
// empty data set. reset the current scroll and report zero size
var layoutWidth = constraints.minWidth
@@ -257,7 +260,7 @@
}
// the initial offset for lines from visibleLines list
- require(currentFirstLineScrollOffset >= 0) { "negative initial offset" }
+ requirePrecondition(currentFirstLineScrollOffset >= 0) { "negative initial offset" }
val visibleLinesScrollOffset = -currentFirstLineScrollOffset
var firstLine = visibleLines.first()
@@ -438,24 +441,26 @@
val mainAxisLayoutSize = if (isVertical) layoutHeight else layoutWidth
val hasSpareSpace = finalMainAxisOffset < min(mainAxisLayoutSize, maxOffset)
if (hasSpareSpace) {
- check(firstLineScrollOffset == 0) { "non-zero firstLineScrollOffset" }
+ checkPrecondition(firstLineScrollOffset == 0) { "non-zero firstLineScrollOffset" }
}
val positionedItems = ArrayList<LazyGridMeasuredItem>(lines.fastSumBy { it.items.size })
if (hasSpareSpace) {
- require(itemsBefore.isEmpty() && itemsAfter.isEmpty()) { "no items" }
+ requirePrecondition(itemsBefore.isEmpty() && itemsAfter.isEmpty()) { "no items" }
val linesCount = lines.size
fun Int.reverseAware() = if (!reverseLayout) this else linesCount - this - 1
val sizes = IntArray(linesCount) { index -> lines[index.reverseAware()].mainAxisSize }
val offsets = IntArray(linesCount) { 0 }
if (isVertical) {
- with(requireNotNull(verticalArrangement) { "null verticalArrangement" }) {
+ with(requirePreconditionNotNull(verticalArrangement) { "null verticalArrangement" }) {
density.arrange(mainAxisLayoutSize, sizes, offsets)
}
} else {
- with(requireNotNull(horizontalArrangement) { "null horizontalArrangement" }) {
+ with(
+ requirePreconditionNotNull(horizontalArrangement) { "null horizontalArrangement" }
+ ) {
// Enforces Ltr layout direction as it is mirrored with placeRelative later.
density.arrange(mainAxisLayoutSize, sizes, LayoutDirection.Ltr, offsets)
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItem.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItem.kt
index 2e5cfcb..72c3a2b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItem.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItem.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.lazy.grid
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimator
import androidx.compose.foundation.lazy.layout.LazyLayoutMeasuredItem
import androidx.compose.ui.graphics.layer.GraphicsLayer
@@ -181,7 +182,7 @@
scope: Placeable.PlacementScope,
) =
with(scope) {
- require(mainAxisLayoutSize != Unset) { "position() should be called first" }
+ requirePrecondition(mainAxisLayoutSize != Unset) { "position() should be called first" }
repeat(placeablesCount) { index ->
val placeable = placeables[index]
val minOffset = minMainAxisOffset - placeable.mainAxisSize
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt
index f4716a1..5c33a25 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.lazy.grid
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.lazy.layout.LazyLayoutKeyIndexMap
import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
import androidx.compose.foundation.lazy.layout.LazyLayoutMeasuredItemProvider
@@ -62,7 +63,7 @@
if (constraints.hasFixedWidth) {
constraints.minWidth
} else {
- require(constraints.hasFixedHeight) { "does not have fixed height" }
+ requirePrecondition(constraints.hasFixedHeight) { "does not have fixed height" }
constraints.minHeight
}
return createItem(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt
index 36a2a29..e58b89d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt
@@ -17,6 +17,8 @@
package androidx.compose.foundation.lazy.grid
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.lazy.layout.LazyLayoutNearestRangeState
import androidx.compose.foundation.lazy.layout.findIndexByKey
import androidx.compose.runtime.getValue
@@ -55,7 +57,9 @@
if (hadFirstNotEmptyLayout || measureResult.totalItemsCount > 0) {
hadFirstNotEmptyLayout = true
val scrollOffset = measureResult.firstVisibleLineScrollOffset
- check(scrollOffset >= 0f) { "scrollOffset should be non-negative ($scrollOffset)" }
+ checkPrecondition(scrollOffset >= 0f) {
+ "scrollOffset should be non-negative ($scrollOffset)"
+ }
val firstIndex = measureResult.firstVisibleLine?.items?.firstOrNull()?.index ?: 0
update(firstIndex, scrollOffset)
@@ -63,7 +67,7 @@
}
fun updateScrollOffset(scrollOffset: Int) {
- check(scrollOffset >= 0f) { "scrollOffset should be non-negative ($scrollOffset)" }
+ checkPrecondition(scrollOffset >= 0f) { "scrollOffset should be non-negative" }
this.scrollOffset = scrollOffset
}
@@ -104,7 +108,7 @@
}
private fun update(index: Int, scrollOffset: Int) {
- require(index >= 0f) { "Index should be non-negative ($index)" }
+ requirePrecondition(index >= 0f) { "Index should be non-negative" }
this.index = index
nearestRangeState.update(index)
this.scrollOffset = scrollOffset
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpan.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpan.kt
index 648f756..26b71f9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpan.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpan.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.lazy.grid
import androidx.annotation.IntRange
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.runtime.Immutable
/** Represents the span of an item in a [LazyVerticalGrid] or a [LazyHorizontalGrid]. */
@@ -36,7 +37,7 @@
* an item of a [LazyVerticalGrid] and the vertical span for a [LazyHorizontalGrid].
*/
fun GridItemSpan(@IntRange(from = 1) currentLineSpan: Int): GridItemSpan {
- require(currentLineSpan > 0) { "The span value should be higher than 0" }
+ requirePrecondition(currentLineSpan > 0) { "The span value should be higher than 0" }
return GridItemSpan(currentLineSpan.toLong())
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt
index c369ae6..b9dcbb46 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt
@@ -16,6 +16,8 @@
package androidx.compose.foundation.lazy.grid
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
import kotlin.math.min
import kotlin.math.sqrt
@@ -111,7 +113,7 @@
cachedBucket.clear()
}
- check(currentLine <= lineIndex) { "currentLine > lineIndex" }
+ checkPrecondition(currentLine <= lineIndex) { "currentLine > lineIndex" }
while (currentLine < lineIndex && currentItemIndex < totalSize) {
if (cacheThisBucket) {
@@ -138,7 +140,7 @@
if (currentLine % bucketSize == 0 && currentItemIndex < totalSize) {
val currentLineBucket = currentLine / bucketSize
// This should happen, as otherwise this should have been used as starting point.
- check(buckets.size == currentLineBucket) { "invalid starting point" }
+ checkPrecondition(buckets.size == currentLineBucket) { "invalid starting point" }
buckets.add(Bucket(currentItemIndex, knownCurrentItemSpan))
}
}
@@ -172,7 +174,7 @@
if (totalSize <= 0) {
return 0
}
- require(itemIndex < totalSize) { "ItemIndex > total count" }
+ requirePrecondition(itemIndex < totalSize) { "ItemIndex > total count" }
if (!gridContent.hasCustomSpans) {
return itemIndex / slotsPerLine
}
@@ -184,7 +186,7 @@
var currentLine = lowerBoundBucket * bucketSize
var currentItemIndex = buckets[lowerBoundBucket].firstItemIndex
- require(currentItemIndex <= itemIndex) { "currentItemIndex > itemIndex" }
+ requirePrecondition(currentItemIndex <= itemIndex) { "currentItemIndex > itemIndex" }
var spansUsed = 0
while (currentItemIndex < itemIndex) {
val span = spanOf(currentItemIndex++, slotsPerLine - spansUsed)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
index eb14a92..fc0832a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
@@ -25,6 +25,7 @@
import androidx.compose.foundation.gestures.stopScroll
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.foundation.lazy.layout.AwaitFirstLayoutModifier
import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimator
@@ -385,8 +386,8 @@
if (distance < 0 && !canScrollForward || distance > 0 && !canScrollBackward) {
return 0f
}
- check(abs(scrollToBeConsumed) <= 0.5f) {
- "entered drag with non-zero pending scroll: $scrollToBeConsumed"
+ checkPrecondition(abs(scrollToBeConsumed) <= 0.5f) {
+ "entered drag with non-zero pending scroll"
}
scrollToBeConsumed += distance
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/IntervalList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/IntervalList.kt
index 2a5e21e..aa3db4c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/IntervalList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/IntervalList.kt
@@ -16,6 +16,8 @@
package androidx.compose.foundation.lazy.layout
+import androidx.compose.foundation.internal.requirePrecondition
+import androidx.compose.foundation.internal.throwIndexOutOfBoundsException
import androidx.compose.runtime.collection.MutableVector
import androidx.compose.runtime.collection.mutableVectorOf
@@ -75,8 +77,8 @@
val value: T
) {
init {
- require(startIndex >= 0) { "startIndex should be >= 0, but was $startIndex" }
- require(size > 0) { "size should be >0, but was $size" }
+ requirePrecondition(startIndex >= 0) { "startIndex should be >= 0" }
+ requirePrecondition(size > 0) { "size should be > 0" }
}
}
}
@@ -106,7 +108,7 @@
* @param value the value representing this interval.
*/
fun addInterval(size: Int, value: T) {
- require(size >= 0) { "size should be >=0, but was $size" }
+ requirePrecondition(size >= 0) { "size should be >=0" }
if (size == 0) {
return
}
@@ -128,7 +130,7 @@
override fun forEach(fromIndex: Int, toIndex: Int, block: (IntervalList.Interval<T>) -> Unit) {
checkIndexBounds(fromIndex)
checkIndexBounds(toIndex)
- require(toIndex >= fromIndex) {
+ requirePrecondition(toIndex >= fromIndex) {
"toIndex ($toIndex) should be not smaller than fromIndex ($fromIndex)"
}
@@ -156,9 +158,10 @@
}
}
- private fun checkIndexBounds(index: Int) {
+ @Suppress("NOTHING_TO_INLINE")
+ private inline fun checkIndexBounds(index: Int) {
if (index !in 0 until size) {
- throw IndexOutOfBoundsException("Index $index, size $size")
+ throwIndexOutOfBoundsException("Index $index, size $size")
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt
index 43e7e4c..c0c8cdc 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.lazy.layout
+import androidx.collection.mutableObjectIntMapOf
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -84,7 +85,7 @@
private class LazyLayoutItemReusePolicy(private val factory: LazyLayoutItemContentFactory) :
SubcomposeSlotReusePolicy {
- private val countPerType = mutableMapOf<Any?, Int>()
+ private val countPerType = mutableObjectIntMapOf<Any?>()
override fun getSlotsToRetain(slotIds: SubcomposeSlotReusePolicy.SlotIdsSet) {
countPerType.clear()
@@ -92,7 +93,7 @@
while (hasNext()) {
val slotId = next()
val type = factory.getContentType(slotId)
- val currentCount = countPerType[type] ?: 0
+ val currentCount = countPerType.getOrDefault(type, 0)
if (currentCount == MaxItemsToRetainForReuse) {
remove()
} else {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimateScroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimateScroll.kt
index 03125f9..c889773 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimateScroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimateScroll.kt
@@ -21,6 +21,7 @@
import androidx.compose.animation.core.animateTo
import androidx.compose.animation.core.copy
import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import kotlin.coroutines.cancellation.CancellationException
@@ -104,7 +105,7 @@
density: Density,
scrollScope: ScrollScope
) {
- require(index >= 0f) { "Index should be non-negative ($index)" }
+ requirePrecondition(index >= 0f) { "Index should be non-negative" }
with(scrollScope) {
try {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo.kt
index 5403fed..714c98e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.lazy.layout
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.runtime.collection.mutableVectorOf
/**
@@ -76,7 +77,7 @@
minIndex = it.start
}
}
- require(minIndex >= 0) { "negative minIndex" }
+ requirePrecondition(minIndex >= 0) { "negative minIndex" }
return minIndex
}
@@ -101,8 +102,8 @@
val end: Int
) {
init {
- require(start >= 0) { "negative start index" }
- require(end >= start) { "end index greater than start" }
+ requirePrecondition(start >= 0) { "negative start index" }
+ requirePrecondition(end >= start) { "end index greater than start" }
}
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutKeyIndexMap.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutKeyIndexMap.kt
index f5746d1..8ea613b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutKeyIndexMap.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutKeyIndexMap.kt
@@ -19,6 +19,7 @@
import androidx.collection.MutableObjectIntMap
import androidx.collection.ObjectIntMap
import androidx.collection.emptyObjectIntMap
+import androidx.compose.foundation.internal.checkPrecondition
/**
* A key-index mapping used inside the [LazyLayoutItemProvider]. It might not contain all items in
@@ -58,7 +59,7 @@
// all the indexes in the passed [range].
val list = intervalContent.intervals
val first = nearestRange.first
- check(first >= 0) { "negative nearestRange.first" }
+ checkPrecondition(first >= 0) { "negative nearestRange.first" }
val last = minOf(nearestRange.last, list.size - 1)
if (last < first) {
map = emptyObjectIntMap()
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureScope.kt
index d6c9b25..53aeae9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureScope.kt
@@ -16,7 +16,9 @@
package androidx.compose.foundation.lazy.layout
+import androidx.collection.mutableIntObjectMapOf
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.runtime.Stable
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.isSpecified
@@ -65,7 +67,7 @@
@Stable
override fun TextUnit.toDp(): Dp {
- check(type == TextUnitType.Sp) { "Only Sp can convert to Px" }
+ checkPrecondition(type == TextUnitType.Sp) { "Only Sp can convert to Px" }
return Dp(value * fontScale)
}
@@ -109,7 +111,7 @@
* A cache of the previously composed items. It allows us to support [get] re-executions with
* the same index during the same measure pass.
*/
- private val placeablesCache = hashMapOf<Int, List<Placeable>>()
+ private val placeablesCache = mutableIntObjectMapOf<List<Placeable>>()
override fun measure(index: Int, constraints: Constraints): List<Placeable> {
val cachedPlaceable = placeablesCache[index]
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt
index 3ad177c5..354e747 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.lazy.layout
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
@@ -141,7 +142,7 @@
}
override fun release() {
- check(pinsCount > 0) { "Release should only be called once" }
+ checkPrecondition(pinsCount > 0) { "Release should only be called once" }
pinsCount--
if (pinsCount == 0) {
pinnedItemList.release(this)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
index 42d7668..78b15e44 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
@@ -18,6 +18,9 @@
import androidx.collection.mutableObjectLongMapOf
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
+import androidx.compose.foundation.internal.requirePreconditionNotNull
import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState.PrefetchHandle
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
@@ -363,11 +366,11 @@
}
private fun performComposition() {
- require(isValid) {
+ requirePrecondition(isValid) {
"Callers should check whether the request is still valid before calling " +
"performComposition()"
}
- require(precomposeHandle == null) { "Request was already composed!" }
+ requirePrecondition(precomposeHandle == null) { "Request was already composed!" }
val itemProvider = itemContentFactory.itemProvider()
val key = itemProvider.getKey(index)
val contentType = itemProvider.getContentType(index)
@@ -376,14 +379,14 @@
}
private fun performMeasure(constraints: Constraints) {
- require(!isCanceled) {
+ requirePrecondition(!isCanceled) {
"Callers should check whether the request is still valid before calling " +
"performMeasure()"
}
- require(!isMeasured) { "Request was already measured!" }
+ requirePrecondition(!isMeasured) { "Request was already measured!" }
isMeasured = true
val handle =
- requireNotNull(precomposeHandle) {
+ requirePreconditionNotNull(precomposeHandle) {
"performComposition() must be called before performMeasure()"
}
repeat(handle.placeablesCount) { placeableIndex ->
@@ -393,7 +396,7 @@
private fun resolveNestedPrefetchStates(): NestedPrefetchController? {
val precomposedSlotHandle =
- requireNotNull(precomposeHandle) {
+ requirePreconditionNotNull(precomposeHandle) {
"Should precompose before resolving nested prefetch states"
}
@@ -422,7 +425,7 @@
private var requestIndex: Int = 0
init {
- require(states.isNotEmpty()) {
+ requirePrecondition(states.isNotEmpty()) {
"NestedPrefetchController shouldn't be created with no states"
}
}
@@ -431,7 +434,9 @@
if (stateIndex >= states.size) {
return false
}
- check(!isCanceled) { "Should not execute nested prefetch on canceled request" }
+ checkPrecondition(!isCanceled) {
+ "Should not execute nested prefetch on canceled request"
+ }
trace("compose:lazy:prefetch:nested") {
while (stateIndex < states.size) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
index a373688..1fead77 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
@@ -18,6 +18,7 @@
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.node.ModifierNodeElement
@@ -205,7 +206,7 @@
if (userScrollEnabled) {
{ index ->
val itemProvider = itemProviderLambda()
- require(index >= 0 && index < itemProvider.itemCount) {
+ requirePrecondition(index >= 0 && index < itemProvider.itemCount) {
"Can't scroll to index $index, it is out of " +
"bounds [0, ${itemProvider.itemCount})"
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazySaveableStateHolder.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazySaveableStateHolder.kt
index 266a68a..f9636a0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazySaveableStateHolder.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazySaveableStateHolder.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.lazy.layout
+import androidx.compose.foundation.internal.requirePreconditionNotNull
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
@@ -73,7 +74,8 @@
@Composable
override fun SaveableStateProvider(key: Any, content: @Composable () -> Unit) {
- requireNotNull(wrappedHolder) { "null wrappedHolder" }.SaveableStateProvider(key, content)
+ requirePreconditionNotNull(wrappedHolder) { "null wrappedHolder" }
+ .SaveableStateProvider(key, content)
DisposableEffect(key) {
previouslyComposedKeys -= key
onDispose { previouslyComposedKeys += key }
@@ -81,7 +83,7 @@
}
override fun removeState(key: Any) {
- requireNotNull(wrappedHolder) { "null wrappedHolder" }.removeState(key)
+ requirePreconditionNotNull(wrappedHolder) { "null wrappedHolder" }.removeState(key)
}
companion object {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridCells.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridCells.kt
index 26e2c0d..d549cd6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridCells.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridCells.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.lazy.staggeredgrid
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.runtime.Stable
import androidx.compose.ui.unit.Density
@@ -54,7 +55,7 @@
*/
class Fixed(private val count: Int) : StaggeredGridCells {
init {
- require(count > 0) { "grid with no rows/columns" }
+ requirePrecondition(count > 0) { "grid with no rows/columns" }
}
override fun Density.calculateCrossAxisCellSizes(
@@ -84,7 +85,7 @@
*/
class Adaptive(private val minSize: Dp) : StaggeredGridCells {
init {
- require(minSize > 0.dp) { "invalid minSize" }
+ requirePrecondition(minSize > 0.dp) { "invalid minSize" }
}
override fun Density.calculateCrossAxisCellSizes(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
index 9606d9e..edb7f89 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
@@ -19,6 +19,7 @@
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
@@ -101,7 +102,7 @@
contentPadding,
) {
LazyStaggeredGridSlotCache { constraints ->
- require(constraints.maxWidth != Constraints.Infinity) {
+ requirePrecondition(constraints.maxWidth != Constraints.Infinity) {
"LazyVerticalStaggeredGrid's width should be bound by parent."
}
val horizontalPadding =
@@ -192,7 +193,7 @@
contentPadding,
) {
LazyStaggeredGridSlotCache { constraints ->
- require(constraints.maxHeight != Constraints.Infinity) {
+ requirePrecondition(constraints.maxHeight != Constraints.Infinity) {
"LazyHorizontalStaggeredGrid's height should be bound by parent."
}
val verticalPadding =
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfo.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfo.kt
index 44826b1..6c029fe 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfo.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfo.kt
@@ -16,6 +16,8 @@
package androidx.compose.foundation.lazy.staggeredgrid
+import androidx.compose.foundation.internal.requirePrecondition
+
/**
* Utility class to remember grid lane assignments in a sliding window relative to requested item
* position (usually reflected by scroll position). Remembers the maximum range of remembered items
@@ -31,7 +33,7 @@
/** Sets given lane for given item index. */
fun setLane(itemIndex: Int, lane: Int) {
- require(itemIndex >= 0) { "Negative lanes are not supported" }
+ requirePrecondition(itemIndex >= 0) { "Negative lanes are not supported" }
ensureValidIndex(itemIndex)
lanes[itemIndex - anchor] = lane + 1
}
@@ -185,7 +187,7 @@
}
private fun ensureCapacity(capacity: Int, newOffset: Int = 0) {
- require(capacity <= MaxCapacity) {
+ requirePrecondition(capacity <= MaxCapacity) {
"Requested item capacity $capacity is larger than max supported: $MaxCapacity!"
}
if (lanes.size < capacity) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
index 1c54460..20061ed 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.lazy.staggeredgrid
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimator
import androidx.compose.foundation.lazy.layout.LazyLayoutKeyIndexMap
import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
@@ -1226,7 +1227,7 @@
fun place(scope: Placeable.PlacementScope, context: LazyStaggeredGridMeasureContext) =
with(context) {
- require(mainAxisLayoutSize != Unset) { "position() should be called first" }
+ requirePrecondition(mainAxisLayoutSize != Unset) { "position() should be called first" }
with(scope) {
placeables.fastForEachIndexed { index, placeable ->
val minOffset = minMainAxisOffset - placeable.mainAxisSize
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
index d824897..bba2028 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
@@ -25,6 +25,8 @@
import androidx.compose.foundation.gestures.stopScroll
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.lazy.layout.AwaitFirstLayoutModifier
import androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope
import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
@@ -248,8 +250,8 @@
if (distance < 0 && !canScrollForward || distance > 0 && !canScrollBackward) {
return 0f
}
- check(abs(scrollToBeConsumed) <= 0.5f) {
- "entered drag with non-zero pending scroll: $scrollToBeConsumed"
+ checkPrecondition(abs(scrollToBeConsumed) <= 0.5f) {
+ "entered drag with non-zero pending scroll"
}
scrollToBeConsumed += distance
@@ -540,7 +542,7 @@
FullSpan -> 0
// lane was previously set, keep item to the same lane
else -> {
- require(previousLane >= 0) {
+ requirePrecondition(previousLane >= 0) {
"Expected positive lane number, got $previousLane instead."
}
minOf(previousLane, laneCount)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
index 6e46ec2..ad3daca 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
@@ -30,6 +30,7 @@
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.snapping.SnapPosition
import androidx.compose.foundation.gestures.snapping.snapFlingBehavior
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.layout.IntervalList
import androidx.compose.foundation.lazy.layout.LazyLayout
@@ -100,7 +101,7 @@
/** The content of the pager */
pageContent: @Composable PagerScope.(page: Int) -> Unit
) {
- require(beyondViewportPageCount >= 0) {
+ requirePrecondition(beyondViewportPageCount >= 0) {
"beyondViewportPageCount should be greater than or equal to 0, " +
"you selected $beyondViewportPageCount"
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/MeasuredPage.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/MeasuredPage.kt
index fcbe242..2146096 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/MeasuredPage.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/MeasuredPage.kt
@@ -17,6 +17,8 @@
package androidx.compose.foundation.pager
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.internal.requirePrecondition
+import androidx.compose.foundation.internal.requirePreconditionNotNull
import androidx.compose.ui.Alignment
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.unit.IntOffset
@@ -67,14 +69,14 @@
val indexInArray = index * 2
if (isVertical) {
placeableOffsets[indexInArray] =
- requireNotNull(horizontalAlignment) { "null horizontalAlignment" }
+ requirePreconditionNotNull(horizontalAlignment) { "null horizontalAlignment" }
.align(placeable.width, layoutWidth, layoutDirection)
placeableOffsets[indexInArray + 1] = mainAxisOffset
mainAxisOffset += placeable.height
} else {
placeableOffsets[indexInArray] = mainAxisOffset
placeableOffsets[indexInArray + 1] =
- requireNotNull(verticalAlignment) { "null verticalAlignment" }
+ requirePreconditionNotNull(verticalAlignment) { "null verticalAlignment" }
.align(placeable.height, layoutHeight)
mainAxisOffset += placeable.width
}
@@ -83,7 +85,7 @@
fun place(scope: Placeable.PlacementScope) =
with(scope) {
- require(mainAxisLayoutSize != Unset) { "position() should be called first" }
+ requirePrecondition(mainAxisLayoutSize != Unset) { "position() should be called first" }
repeat(placeables.size) { index ->
val placeable = placeables[index]
var offset = getOffset(index)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
index 7cfd681..6d51907 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
@@ -30,6 +30,7 @@
import androidx.compose.foundation.gestures.snapping.SnapPosition
import androidx.compose.foundation.gestures.snapping.calculateFinalSnappingBound
import androidx.compose.foundation.gestures.snapping.snapFlingBehavior
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@@ -293,7 +294,7 @@
),
@FloatRange(from = 0.0, to = 1.0) snapPositionalThreshold: Float = 0.5f
): TargetedFlingBehavior {
- require(snapPositionalThreshold in 0f..1f) {
+ requirePrecondition(snapPositionalThreshold in 0f..1f) {
"snapPositionalThreshold should be a number between 0 and 1. " +
"You've specified $snapPositionalThreshold"
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
index 5bdfc9b..1333583 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
@@ -20,6 +20,8 @@
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.snapping.SnapPosition
import androidx.compose.foundation.gestures.snapping.calculateDistanceToDesiredSnapPosition
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
@@ -62,8 +64,8 @@
coroutineScope: CoroutineScope,
layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
): PagerMeasureResult {
- require(beforeContentPadding >= 0) { "negative beforeContentPadding" }
- require(afterContentPadding >= 0) { "negative afterContentPadding" }
+ requirePrecondition(beforeContentPadding >= 0) { "negative beforeContentPadding" }
+ requirePrecondition(afterContentPadding >= 0) { "negative afterContentPadding" }
val pageSizeWithSpacing = (pageAvailableSize + spaceBetweenPages).coerceAtLeast(0)
debugLog {
@@ -296,7 +298,9 @@
}
// the initial offset for pages from visiblePages list
- require(currentFirstPageScrollOffset >= 0) { "invalid currentFirstPageScrollOffset" }
+ requirePrecondition(currentFirstPageScrollOffset >= 0) {
+ "invalid currentFirstPageScrollOffset"
+ }
val visiblePagesScrollOffset = -currentFirstPageScrollOffset
var firstPage = visiblePages.first()
@@ -612,13 +616,17 @@
val mainAxisLayoutSize = if (orientation == Orientation.Vertical) layoutHeight else layoutWidth
val hasSpareSpace = finalMainAxisOffset < minOf(mainAxisLayoutSize, maxOffset)
if (hasSpareSpace) {
- check(pagesScrollOffset == 0) { "non-zero pagesScrollOffset=$pagesScrollOffset" }
+ checkPrecondition(pagesScrollOffset == 0) {
+ "non-zero pagesScrollOffset=$pagesScrollOffset"
+ }
}
val positionedPages =
ArrayList<MeasuredPage>(pages.size + extraPagesBefore.size + extraPagesAfter.size)
if (hasSpareSpace) {
- require(extraPagesBefore.isEmpty() && extraPagesAfter.isEmpty()) { "No extra pages" }
+ requirePrecondition(extraPagesBefore.isEmpty() && extraPagesAfter.isEmpty()) {
+ "No extra pages"
+ }
val pagesCount = pages.size
fun Int.reverseAware() = if (!reverseLayout) this else pagesCount - this - 1
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSnapDistance.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSnapDistance.kt
index 17932be..4063c19 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSnapDistance.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSnapDistance.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.pager
import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.runtime.Stable
/**
@@ -55,7 +56,7 @@
* @param pages The maximum number of extra pages that can be flung at once.
*/
fun atMost(pages: Int): PagerSnapDistance {
- require(pages >= 0) {
+ requirePrecondition(pages >= 0) {
"pages should be greater than or equal to 0. You have used $pages."
}
return PagerSnapDistanceMaxPages(pages)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
index 5e1de2b..cffd236 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
@@ -30,6 +30,7 @@
import androidx.compose.foundation.gestures.stopScroll
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.lazy.layout.AwaitFirstLayoutModifier
import androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope
import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
@@ -168,7 +169,7 @@
abstract val pageCount: Int
init {
- require(currentPageOffsetFraction in -0.5..0.5) {
+ requirePrecondition(currentPageOffsetFraction in -0.5..0.5) {
"currentPageOffsetFraction $currentPageOffsetFraction is " +
"not within the range -0.5 to 0.5"
}
@@ -478,7 +479,7 @@
) = scroll {
debugLog { "Scroll from page=$currentPage to page=$page" }
awaitScrollDependencies()
- require(pageOffsetFraction in -0.5..0.5) {
+ requirePrecondition(pageOffsetFraction in -0.5..0.5) {
"pageOffsetFraction $pageOffsetFraction is not within the range -0.5 to 0.5"
}
val targetPage = page.coerceInPageRange()
@@ -581,7 +582,7 @@
)
return
awaitScrollDependencies()
- require(pageOffsetFraction in -0.5..0.5) {
+ requirePrecondition(pageOffsetFraction in -0.5..0.5) {
"pageOffsetFraction $pageOffsetFraction is not within the range -0.5 to 0.5"
}
val targetPage = page.coerceInPageRange()
@@ -787,7 +788,9 @@
* @return The offset of [page] with respect to [currentPage].
*/
fun getOffsetDistanceInPages(page: Int): Float {
- require(page in 0..pageCount) { "page $page is not within the range 0 to $pageCount" }
+ requirePrecondition(page in 0..pageCount) {
+ "page $page is not within the range 0 to $pageCount"
+ }
return page - currentPage - currentPageOffsetFraction
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerBasedShape.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerBasedShape.kt
index 2650ff4..9e8e967 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerBasedShape.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerBasedShape.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.shape
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Shape
@@ -58,7 +59,9 @@
topEnd *= scale
bottomEnd *= scale
}
- require(topStart >= 0.0f && topEnd >= 0.0f && bottomEnd >= 0.0f && bottomStart >= 0.0f) {
+ requirePrecondition(
+ topStart >= 0.0f && topEnd >= 0.0f && bottomEnd >= 0.0f && bottomStart >= 0.0f
+ ) {
"Corner size in Px can't be negative(topStart = $topStart, topEnd = $topEnd, " +
"bottomEnd = $bottomEnd, bottomStart = $bottomStart)!"
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/HeightInLinesModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/HeightInLinesModifier.kt
index 3cd43d0..3522308 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/HeightInLinesModifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/HeightInLinesModifier.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.text
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.layout.heightIn
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -119,10 +120,10 @@
}
internal fun validateMinMaxLines(minLines: Int, maxLines: Int) {
- require(minLines > 0 && maxLines > 0) {
+ requirePrecondition(minLines > 0 && maxLines > 0) {
"both minLines $minLines and maxLines $maxLines must be greater than zero"
}
- require(minLines <= maxLines) {
+ requirePrecondition(minLines <= maxLines) {
"minLines $minLines must be less than or equal to maxLines $maxLines"
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/InlineTextContent.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/InlineTextContent.kt
index c4c4ee8..6cd8718 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/InlineTextContent.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/InlineTextContent.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.text
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.text.AnnotatedString
@@ -48,7 +49,7 @@
id: String,
alternateText: String = REPLACEMENT_CHAR
) {
- require(alternateText.isNotEmpty()) { "alternateText can't be an empty string." }
+ requirePrecondition(alternateText.isNotEmpty()) { "alternateText can't be an empty string." }
pushStringAnnotation(INLINE_CONTENT_TAG, id)
append(alternateText)
pop()
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
index 75f76f8..b3125c6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.text
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.text.TextDelegate.Companion.paint
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.Canvas
@@ -112,9 +113,9 @@
get() = nonNullIntrinsics.maxIntrinsicWidth.ceilToIntPx()
init {
- require(maxLines > 0) { "no maxLines" }
- require(minLines > 0) { "no minLines" }
- require(minLines <= maxLines) { "minLines greater than maxLines" }
+ requirePrecondition(maxLines > 0) { "no maxLines" }
+ requirePrecondition(minLines > 0) { "no minLines" }
+ requirePrecondition(minLines <= maxLines) { "minLines greater than maxLines" }
}
fun layoutIntrinsics(layoutDirection: LayoutDirection) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/ValidatingOffsetMapping.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/ValidatingOffsetMapping.kt
index 3bcd0d1..b6261ae 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/ValidatingOffsetMapping.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/ValidatingOffsetMapping.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.text
import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.input.OffsetMapping
import androidx.compose.ui.text.input.TransformedText
@@ -113,7 +114,7 @@
}
private fun validateTransformedToOriginal(originalOffset: Int, originalLength: Int, offset: Int) {
- check(originalOffset in 0..originalLength) {
+ checkPrecondition(originalOffset in 0..originalLength) {
"OffsetMapping.transformedToOriginal returned invalid mapping: " +
"$offset -> $originalOffset is not in range of original text " +
"[0, $originalLength]"
@@ -125,7 +126,7 @@
transformedLength: Int,
offset: Int
) {
- check(transformedOffset in 0..transformedLength) {
+ checkPrecondition(transformedOffset in 0..transformedLength) {
"OffsetMapping.originalToTransformed returned invalid mapping: " +
"$offset -> $transformedOffset is not in range of transformed text " +
"[0, $transformedLength]"
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt
index 7fc64b1..77ec773 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.text.input
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Stable
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
@@ -222,7 +223,7 @@
private data class MaxLengthFilter(private val maxLength: Int) : InputTransformation {
init {
- require(maxLength >= 0) { "maxLength must be at least zero, was $maxLength" }
+ requirePrecondition(maxLength >= 0) { "maxLength must be at least zero" }
}
override fun SemanticsPropertyReceiver.applySemantics() {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt
index 9867e6d..85cbe37 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.text.input
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.text.input.TextFieldBuffer.ChangeList
import androidx.compose.foundation.text.input.internal.ChangeTracker
import androidx.compose.foundation.text.input.internal.OffsetMappingCalculator
@@ -162,8 +163,10 @@
textStart: Int = 0,
textEnd: Int = text.length
) {
- require(start <= end) { "Expected start=$start <= end=$end" }
- require(textStart <= textEnd) { "Expected textStart=$textStart <= textEnd=$textEnd" }
+ requirePrecondition(start <= end) { "Expected start=$start <= end=$end" }
+ requirePrecondition(textStart <= textEnd) {
+ "Expected textStart=$textStart <= textEnd=$textEnd"
+ }
onTextWillChange(start, end, textEnd - textStart)
buffer.replace(start, end, text, textStart, textEnd)
}
@@ -341,12 +344,12 @@
val start = if (startExclusive) 0 else -1
val end = if (endExclusive) length else length + 1
- require(index in start until end) { "Expected $index to be in [$start, $end)" }
+ requirePrecondition(index in start until end) { "Expected $index to be in [$start, $end)" }
}
private fun requireValidRange(range: TextRange) {
val validRange = TextRange(0, length)
- require(range in validRange) { "Expected $range to be in $validRange" }
+ requirePrecondition(range in validRange) { "Expected $range to be in $validRange" }
}
/**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldLineLimits.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldLineLimits.kt
index 772b2bf..f7637e9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldLineLimits.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldLineLimits.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.text.input
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
import androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine
@@ -57,7 +58,7 @@
class MultiLine(val minHeightInLines: Int = 1, val maxHeightInLines: Int = Int.MAX_VALUE) :
TextFieldLineLimits {
init {
- require(minHeightInLines in 1..maxHeightInLines) {
+ requirePrecondition(minHeightInLines in 1..maxHeightInLines) {
"Expected 1 ≤ minHeightInLines ≤ maxHeightInLines, were " +
"$minHeightInLines, $maxHeightInLines"
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
index 5d9b251a..d23fdb7 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
@@ -20,6 +20,7 @@
import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.foundation.text.input.internal.EditingBuffer
import androidx.compose.foundation.text.input.internal.undo.TextFieldEditUndoBehavior
import androidx.compose.runtime.Composable
@@ -200,7 +201,9 @@
@PublishedApi
internal fun startEdit(): TextFieldBuffer {
val isEditingFreeze = Snapshot.withoutReadObservation { isEditing }
- check(!isEditingFreeze) { "TextFieldState does not support concurrent or nested editing." }
+ checkPrecondition(!isEditingFreeze) {
+ "TextFieldState does not support concurrent or nested editing."
+ }
isEditing = true
return TextFieldBuffer(value)
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditCommand.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditCommand.kt
index 058ed63..970b93c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditCommand.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditCommand.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.text.input.internal
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.text.findFollowingBreak
import androidx.compose.foundation.text.findPrecedingBreak
import androidx.compose.foundation.text.input.PlacedAnnotation
@@ -148,7 +149,7 @@
* be non-negative.
*/
internal fun EditingBuffer.deleteSurroundingText(lengthBeforeCursor: Int, lengthAfterCursor: Int) {
- require(lengthBeforeCursor >= 0 && lengthAfterCursor >= 0) {
+ requirePrecondition(lengthBeforeCursor >= 0 && lengthAfterCursor >= 0) {
"Expected lengthBeforeCursor and lengthAfterCursor to be non-negative, were " +
"$lengthBeforeCursor and $lengthAfterCursor respectively."
}
@@ -182,7 +183,7 @@
lengthBeforeCursor: Int,
lengthAfterCursor: Int
) {
- require(lengthBeforeCursor >= 0 && lengthAfterCursor >= 0) {
+ requirePrecondition(lengthBeforeCursor >= 0 && lengthAfterCursor >= 0) {
"Expected lengthBeforeCursor and lengthAfterCursor to be non-negative, were " +
"$lengthBeforeCursor and $lengthAfterCursor respectively."
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditingBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditingBuffer.kt
index 198f266..2c72cbd 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditingBuffer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditingBuffer.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.text.input.internal
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.foundation.text.input.PlacedAnnotation
import androidx.compose.foundation.text.input.TextHighlightType
import androidx.compose.runtime.collection.MutableVector
@@ -54,7 +55,7 @@
/** The inclusive selection start offset */
var selectionStart = selection.start
private set(value) {
- require(value >= 0) { "Cannot set selectionStart to a negative value: $value" }
+ requirePrecondition(value >= 0) { "Cannot set selectionStart to a negative value" }
field = value
highlight = null
}
@@ -62,7 +63,7 @@
/** The exclusive selection end offset */
var selectionEnd = selection.end
private set(value) {
- require(value >= 0) { "Cannot set selectionEnd to a negative value: $value" }
+ requirePrecondition(value >= 0) { "Cannot set selectionEnd to a negative value" }
field = value
highlight = null
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/GapBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/GapBuffer.kt
index 98e60df..cc6ef10 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/GapBuffer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/GapBuffer.kt
@@ -16,6 +16,8 @@
package androidx.compose.foundation.text.input.internal
+import androidx.compose.foundation.internal.requirePrecondition
+
/**
* The gap buffer implementation
*
@@ -226,10 +228,10 @@
textStart: Int = 0,
textEnd: Int = text.length
) {
- require(start <= end) { "start=$start > end=$end" }
- require(textStart <= textEnd) { "textStart=$textStart > textEnd=$textEnd" }
- require(start >= 0) { "start must be non-negative, but was $start" }
- require(textStart >= 0) { "textStart must be non-negative, but was $textStart" }
+ requirePrecondition(start <= end) { "start=$start > end=$end" }
+ requirePrecondition(textStart <= textEnd) { "textStart=$textStart > textEnd=$textEnd" }
+ requirePrecondition(start >= 0) { "start must be non-negative, but was $start" }
+ requirePrecondition(textStart >= 0) { "textStart must be non-negative, but was $textStart" }
val buffer = buffer
val textLength = textEnd - textStart
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.kt
index dd325d1..dc5d45f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.kt
@@ -18,6 +18,7 @@
package androidx.compose.foundation.text.input.internal
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.foundation.text.LegacyTextFieldState
import androidx.compose.foundation.text.selection.TextFieldSelectionManager
import androidx.compose.ui.layout.LayoutCoordinates
@@ -47,12 +48,14 @@
private set
fun registerModifier(node: LegacyPlatformTextInputNode) {
- check(textInputModifierNode == null) { "Expected textInputModifierNode to be null" }
+ checkPrecondition(textInputModifierNode == null) {
+ "Expected textInputModifierNode to be null"
+ }
textInputModifierNode = node
}
fun unregisterModifier(node: LegacyPlatformTextInputNode) {
- check(textInputModifierNode === node) {
+ checkPrecondition(textInputModifierNode === node) {
"Expected textInputModifierNode to be $node but was $textInputModifierNode"
}
textInputModifierNode = null
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/OffsetMappingCalculator.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/OffsetMappingCalculator.kt
index 423d93a..b6fb607 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/OffsetMappingCalculator.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/OffsetMappingCalculator.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.text.input.internal
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.ui.text.TextRange
/**
@@ -262,7 +263,7 @@
* [sourceEnd] (exclusive) in the original text with some text with length [newLength].
*/
fun recordEditOperation(sourceStart: Int, sourceEnd: Int, newLength: Int) {
- require(newLength >= 0) { "Expected newLen to be ≥ 0, was $newLength" }
+ requirePrecondition(newLength >= 0) { "Expected newLen to be ≥ 0, was $newLength" }
val sourceMin = minOf(sourceStart, sourceEnd)
val sourceMax = maxOf(sourceMin, sourceEnd)
val sourceLength = sourceMax - sourceMin
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt
index 6373697..0ae2aae 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.text.input.internal
+import androidx.compose.foundation.internal.checkPreconditionNotNull
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.TextDelegate
import androidx.compose.foundation.text.input.PlacedAnnotation
@@ -143,7 +144,7 @@
)
this.measureInputs = measureInputs
val nonMeasureInputs =
- checkNotNull(nonMeasureInputs) {
+ checkPreconditionNotNull(nonMeasureInputs) {
"Called layoutWithNewMeasureInputs before updateNonMeasureInputs"
}
return getOrComputeLayout(nonMeasureInputs, measureInputs)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManager.kt
index 1c50185..2e8c440 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManager.kt
@@ -16,6 +16,8 @@
package androidx.compose.foundation.text.input.internal.undo
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import androidx.compose.runtime.snapshots.SnapshotStateList
@@ -50,10 +52,9 @@
get() = undoStack.size + redoStack.size
init {
- require(capacity >= 0) { "Capacity must be a positive integer" }
- require(size <= capacity) {
- "Initial list of undo and redo operations have a size=($size) greater " +
- "than the given capacity=($capacity)."
+ requirePrecondition(capacity >= 0) { "Capacity must be a positive integer" }
+ requirePrecondition(size <= capacity) {
+ "Initial list of undo and redo operations have a size greater than the given capacity."
}
}
@@ -74,7 +75,7 @@
* returns, the given item has already been carried to the redo stack.
*/
fun undo(): T {
- check(canUndo) {
+ checkPrecondition(canUndo) {
"It's an error to call undo while there is nothing to undo. " +
"Please first check `canUndo` value before calling the `undo` function."
}
@@ -92,7 +93,7 @@
* returns, the given item has already been carried back to the undo stack.
*/
fun redo(): T {
- check(canRedo) {
+ checkPrecondition(canRedo) {
"It's an error to call redo while there is nothing to redo. " +
"Please first check `canRedo` value before calling the `redo` function."
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt
index 03b6402..c0be0a5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.text.modifiers
+import androidx.compose.foundation.internal.requirePreconditionNotNull
import androidx.compose.foundation.text.DefaultMinLines
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.ColorProducer
@@ -80,7 +81,7 @@
)
init {
- requireNotNull(selectionController) {
+ requirePreconditionNotNull(selectionController) {
"Do not use SelectionCapableStaticTextModifier unless selectionController != null"
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
index 3fa4234..02a1f7d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.text.modifiers
+import androidx.compose.foundation.internal.requirePreconditionNotNull
import androidx.compose.foundation.text.DefaultMinLines
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -397,7 +398,7 @@
val layoutCache = getLayoutCache(this)
val localParagraph =
- requireNotNull(layoutCache.paragraph) {
+ requirePreconditionNotNull(layoutCache.paragraph) {
"no paragraph (layoutCache=$_layoutCache, textSubstitution=$textSubstitution)"
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionLayout.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionLayout.kt
index abf85bc..51810a3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionLayout.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionLayout.kt
@@ -23,6 +23,7 @@
import androidx.collection.longObjectMapOf
import androidx.collection.mutableLongIntMapOf
import androidx.collection.mutableLongObjectMapOf
+import androidx.compose.foundation.internal.checkPrecondition
import androidx.compose.foundation.text.selection.Direction.AFTER
import androidx.compose.foundation.text.selection.Direction.BEFORE
import androidx.compose.foundation.text.selection.Direction.ON
@@ -144,7 +145,7 @@
override val previousSelection: Selection?,
) : SelectionLayout {
init {
- check(infoList.size > 1) {
+ checkPrecondition(infoList.size > 1) {
"MultiSelectionLayout requires an infoList size greater than 1, was ${infoList.size}."
}
}
@@ -221,7 +222,7 @@
if (selection.start.selectableId == selection.end.selectableId) {
// this check, if not passed, leads to exceptions when selection
// highlighting is rendered, so check here instead.
- check(
+ checkPrecondition(
(selection.handlesCrossed && selection.start.offset >= selection.end.offset) ||
(!selection.handlesCrossed && selection.start.offset <= selection.end.offset)
) {
@@ -261,7 +262,7 @@
// this check, if not passed, leads to exceptions when selection
// highlighting is rendered, so check here instead.
- check(minOffset <= maxOffset) {
+ checkPrecondition(minOffset <= maxOffset) {
"minOffset should be less than or equal to maxOffset: $subSelection"
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index 8fc5cb3..e911293af 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -24,6 +24,9 @@
import androidx.compose.foundation.focusable
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.waitForUpOrCancellation
+import androidx.compose.foundation.internal.checkPreconditionNotNull
+import androidx.compose.foundation.internal.requirePrecondition
+import androidx.compose.foundation.internal.requirePreconditionNotNull
import androidx.compose.foundation.text.Handle
import androidx.compose.foundation.text.TextDragObserver
import androidx.compose.foundation.text.input.internal.coerceIn
@@ -385,8 +388,8 @@
/** Returns non-nullable [containerLayoutCoordinates]. */
internal fun requireContainerCoordinates(): LayoutCoordinates {
val coordinates = containerLayoutCoordinates
- requireNotNull(coordinates) { "null coordinates" }
- require(coordinates.isAttached) { "unattached coordinates" }
+ requirePreconditionNotNull(coordinates) { "null coordinates" }
+ requirePrecondition(coordinates.isAttached) { "unattached coordinates" }
return coordinates
}
@@ -658,7 +661,9 @@
val selection = selection!!
val anchor = if (isStartHandle) selection.start else selection.end
val selectable =
- checkNotNull(selectionRegistrar.selectableMap[anchor.selectableId]) {
+ checkPreconditionNotNull(
+ selectionRegistrar.selectableMap[anchor.selectableId]
+ ) {
"SelectionRegistrar should contain the current selection's selectableIds"
}
@@ -666,7 +671,7 @@
// is used to convert the position of the beginning of the drag gesture from the
// composable coordinates to selection container coordinates.
val beginLayoutCoordinates =
- checkNotNull(selectable.getLayoutCoordinates()) {
+ checkPreconditionNotNull(selectable.getLayoutCoordinates()) {
"Current selectable should have layout coordinates."
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
index 95052e3..c681888 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
@@ -20,6 +20,7 @@
import androidx.collection.emptyLongObjectMap
import androidx.collection.mutableLongObjectMapOf
import androidx.compose.foundation.AtomicLong
+import androidx.compose.foundation.internal.requirePrecondition
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.Saver
@@ -99,10 +100,10 @@
override var subselections: LongObjectMap<Selection> by mutableStateOf(emptyLongObjectMap())
override fun subscribe(selectable: Selectable): Selectable {
- require(selectable.selectableId != SelectionRegistrar.InvalidSelectableId) {
+ requirePrecondition(selectable.selectableId != SelectionRegistrar.InvalidSelectableId) {
"The selectable contains an invalid id: ${selectable.selectableId}"
}
- require(!_selectableMap.containsKey(selectable.selectableId)) {
+ requirePrecondition(!_selectableMap.containsKey(selectable.selectableId)) {
"Another selectable with the id: $selectable.selectableId has already subscribed."
}
_selectableMap[selectable.selectableId] = selectable
diff --git a/compose/material/material-ripple/build.gradle b/compose/material/material-ripple/build.gradle
index 417619c..51fe754 100644
--- a/compose/material/material-ripple/build.gradle
+++ b/compose/material/material-ripple/build.gradle
@@ -44,7 +44,7 @@
api(project(":compose:foundation:foundation"))
api(project(":compose:runtime:runtime"))
- implementation(project(":collection:collection"))
+ implementation("androidx.collection:collection:1.4.2")
implementation(project(":compose:animation:animation"))
implementation(project(":compose:ui:ui-util"))
}
diff --git a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/NavigationRailBenchmark.kt b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/NavigationRailBenchmark.kt
index 86a552e..f971352 100644
--- a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/NavigationRailBenchmark.kt
+++ b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/NavigationRailBenchmark.kt
@@ -21,16 +21,20 @@
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ModalExpandedNavigationRail
+import androidx.compose.material3.ModalExpandedNavigationRailState
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
import androidx.compose.material3.WideNavigationRail
import androidx.compose.material3.WideNavigationRailItem
+import androidx.compose.material3.rememberModalExpandedNavigationRailState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.testutils.LayeredComposeTestCase
import androidx.compose.testutils.ToggleableTestCase
import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
@@ -40,6 +44,8 @@
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -52,6 +58,7 @@
private val testCaseFactory = { NavigationRailTestCase() }
private val collapsedWideRailTestCaseFactory = { NavigationRailTestCase(true) }
private val expandedWideRailTestCaseFactory = { NavigationRailTestCase(true, true) }
+ private val modalExpandedRailTestCaseFactory = { ModalExpandedRailTestCase() }
@Test
fun firstPixel() {
@@ -113,6 +120,19 @@
assertOneRecomposition = false,
)
}
+
+ @Test
+ fun modalExpandedNavigationRail_firstPixel() {
+ benchmarkRule.benchmarkToFirstPixel(modalExpandedRailTestCaseFactory)
+ }
+
+ @Test
+ fun modalExpandedNavigationRail_stateChange() {
+ benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+ modalExpandedRailTestCaseFactory,
+ assertOneRecomposition = false,
+ )
+ }
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -181,3 +201,48 @@
}
}
}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+internal class ModalExpandedRailTestCase() : LayeredComposeTestCase(), ToggleableTestCase {
+ private lateinit var state: ModalExpandedNavigationRailState
+ private lateinit var scope: CoroutineScope
+
+ @Composable
+ override fun MeasuredContent() {
+ state = rememberModalExpandedNavigationRailState()
+ scope = rememberCoroutineScope()
+
+ ModalExpandedNavigationRail(
+ onDismissRequest = {},
+ railState = state,
+ ) {
+ WideNavigationRailItem(
+ selected = true,
+ onClick = {},
+ icon = { Spacer(Modifier.size(24.dp)) },
+ railExpanded = true,
+ label = { Spacer(Modifier.size(24.dp)) }
+ )
+ WideNavigationRailItem(
+ selected = false,
+ onClick = {},
+ icon = { Spacer(Modifier.size(24.dp)) },
+ railExpanded = true,
+ label = { Spacer(Modifier.size(24.dp)) }
+ )
+ }
+ }
+
+ @Composable
+ override fun ContentWrappers(content: @Composable () -> Unit) {
+ MaterialExpressiveTheme { content() }
+ }
+
+ override fun toggleState() {
+ if (state.isOpen) {
+ scope.launch { state.close() }
+ } else {
+ scope.launch { state.open() }
+ }
+ }
+}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 2c99698..39be560 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -235,6 +235,22 @@
@androidx.compose.runtime.Stable public final class ButtonElevation {
}
+ public final class ButtonGroupDefaults {
+ method public float getAnimateFraction();
+ method public float getSpaceBetween();
+ property public final float animateFraction;
+ property public final float spaceBetween;
+ field public static final androidx.compose.material3.ButtonGroupDefaults INSTANCE;
+ }
+
+ public final class ButtonGroupKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ButtonGroup(optional androidx.compose.ui.Modifier modifier, optional @FloatRange(from=0.0) float animateFraction, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, kotlin.jvm.functions.Function1<? super androidx.compose.material3.ButtonGroupScope,kotlin.Unit> content);
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public interface ButtonGroupScope {
+ method public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, fromInclusive=false) float weight, optional boolean fill);
+ }
+
public final class ButtonKt {
method @androidx.compose.runtime.Composable public static void Button(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void ElevatedButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
@@ -244,21 +260,15 @@
}
public final class ButtonShapes {
- ctor public ButtonShapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape hoveredShape, androidx.compose.ui.graphics.Shape focusedShape, androidx.compose.ui.graphics.Shape checkedShape);
+ ctor public ButtonShapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
method public androidx.compose.ui.graphics.Shape component1();
method public androidx.compose.ui.graphics.Shape component2();
method public androidx.compose.ui.graphics.Shape component3();
- method public androidx.compose.ui.graphics.Shape component4();
- method public androidx.compose.ui.graphics.Shape component5();
- method public androidx.compose.material3.ButtonShapes copy(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape hoveredShape, androidx.compose.ui.graphics.Shape focusedShape, androidx.compose.ui.graphics.Shape checkedShape);
+ method public androidx.compose.material3.ButtonShapes copy(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
method public androidx.compose.ui.graphics.Shape getCheckedShape();
- method public androidx.compose.ui.graphics.Shape getFocusedShape();
- method public androidx.compose.ui.graphics.Shape getHoveredShape();
method public androidx.compose.ui.graphics.Shape getPressedShape();
method public androidx.compose.ui.graphics.Shape getShape();
property public final androidx.compose.ui.graphics.Shape checkedShape;
- property public final androidx.compose.ui.graphics.Shape focusedShape;
- property public final androidx.compose.ui.graphics.Shape hoveredShape;
property public final androidx.compose.ui.graphics.Shape pressedShape;
property public final androidx.compose.ui.graphics.Shape shape;
}
@@ -957,36 +967,72 @@
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors filledTonalIconToggleButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors filledTonalIconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFilledShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getLargeIconSize();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeRoundShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeSquareShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getMediumIconSize();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumRoundShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumSquareShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
- method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getRoundShape();
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long getSmallContainerSize();
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getSmallIconSize();
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long getSmallNarrowContainerSize();
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long getSmallWideContainerSize();
- method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSquareShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallRoundShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallSquareShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getStandardShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getXLargeIconSize();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeRoundShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeSquareShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getXSmallIconSize();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallRoundShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallSquareShape();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors iconToggleButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors iconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long largeContainerSize(optional int widthOption);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long mediumContainerSize(optional int widthOption);
method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke outlinedIconButtonBorder(boolean enabled);
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors outlinedIconButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors outlinedIconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke? outlinedIconToggleButtonBorder(boolean enabled, boolean checked);
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final long SmallContainerSize;
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float SmallIconSize;
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final long SmallNarrowContainerSize;
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final long SmallWideContainerSize;
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long smallContainerSize(optional int widthOption);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long xLargeContainerSize(optional int widthOption);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long xSmallContainerSize(optional int widthOption);
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape filledShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float largeIconSize;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeRoundShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeSquareShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float mediumIconSize;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumRoundShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumSquareShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedShape;
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape roundShape;
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape squareShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float smallIconSize;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallRoundShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallSquareShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape standardShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float xLargeIconSize;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeRoundShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeSquareShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float xSmallIconSize;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallRoundShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallSquareShape;
field public static final androidx.compose.material3.IconButtonDefaults INSTANCE;
}
+ @kotlin.jvm.JvmInline public static final value class IconButtonDefaults.IconButtonWidthOption {
+ field public static final androidx.compose.material3.IconButtonDefaults.IconButtonWidthOption.Companion Companion;
+ }
+
+ public static final class IconButtonDefaults.IconButtonWidthOption.Companion {
+ method public int getNarrow();
+ method public int getUniform();
+ method public int getWide();
+ property public final int Narrow;
+ property public final int Uniform;
+ property public final int Wide;
+ }
+
public final class IconButtonKt {
method @androidx.compose.runtime.Composable public static void FilledIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void FilledIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
@@ -2516,18 +2562,12 @@
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getCheckedShape();
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedCheckedShape();
- method public androidx.compose.ui.graphics.Shape getElevatedFocusedShape();
- method public androidx.compose.ui.graphics.Shape getElevatedHoveredShape();
method public androidx.compose.ui.graphics.Shape getElevatedPressedShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedShape();
- method public androidx.compose.ui.graphics.Shape getFocusedShape();
- method public androidx.compose.ui.graphics.Shape getHoveredShape();
method public float getIconSize();
method public float getIconSpacing();
method public float getMinHeight();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedCheckedShape();
- method public androidx.compose.ui.graphics.Shape getOutlinedFocusedShape();
- method public androidx.compose.ui.graphics.Shape getOutlinedHoveredShape();
method public androidx.compose.ui.graphics.Shape getOutlinedPressedShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
method public androidx.compose.ui.graphics.Shape getPressedShape();
@@ -2535,13 +2575,11 @@
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSquareShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTonalCheckedShape();
- method public androidx.compose.ui.graphics.Shape getTonalFocusedShape();
- method public androidx.compose.ui.graphics.Shape getTonalHoveredShape();
method public androidx.compose.ui.graphics.Shape getTonalPressedShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTonalShape();
method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors outlinedToggleButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors outlinedToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
- method public androidx.compose.material3.ButtonShapes shapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape hoverShape, androidx.compose.ui.graphics.Shape focusShape, androidx.compose.ui.graphics.Shape checkedShape);
+ method public androidx.compose.material3.ButtonShapes shapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors toggleButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors toggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors tonalToggleButtonColors();
@@ -2552,15 +2590,9 @@
property public final float MinHeight;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape checkedShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape elevatedCheckedShape;
- property public final androidx.compose.ui.graphics.Shape elevatedFocusedShape;
- property public final androidx.compose.ui.graphics.Shape elevatedHoveredShape;
property public final androidx.compose.ui.graphics.Shape elevatedPressedShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape elevatedShape;
- property public final androidx.compose.ui.graphics.Shape focusedShape;
- property public final androidx.compose.ui.graphics.Shape hoveredShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedCheckedShape;
- property public final androidx.compose.ui.graphics.Shape outlinedFocusedShape;
- property public final androidx.compose.ui.graphics.Shape outlinedHoveredShape;
property public final androidx.compose.ui.graphics.Shape outlinedPressedShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedShape;
property public final androidx.compose.ui.graphics.Shape pressedShape;
@@ -2568,8 +2600,6 @@
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape squareShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape tonalCheckedShape;
- property public final androidx.compose.ui.graphics.Shape tonalFocusedShape;
- property public final androidx.compose.ui.graphics.Shape tonalHoveredShape;
property public final androidx.compose.ui.graphics.Shape tonalPressedShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape tonalShape;
field public static final androidx.compose.material3.ToggleButtonDefaults INSTANCE;
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 2c99698..39be560 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -235,6 +235,22 @@
@androidx.compose.runtime.Stable public final class ButtonElevation {
}
+ public final class ButtonGroupDefaults {
+ method public float getAnimateFraction();
+ method public float getSpaceBetween();
+ property public final float animateFraction;
+ property public final float spaceBetween;
+ field public static final androidx.compose.material3.ButtonGroupDefaults INSTANCE;
+ }
+
+ public final class ButtonGroupKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ButtonGroup(optional androidx.compose.ui.Modifier modifier, optional @FloatRange(from=0.0) float animateFraction, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, kotlin.jvm.functions.Function1<? super androidx.compose.material3.ButtonGroupScope,kotlin.Unit> content);
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public interface ButtonGroupScope {
+ method public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, fromInclusive=false) float weight, optional boolean fill);
+ }
+
public final class ButtonKt {
method @androidx.compose.runtime.Composable public static void Button(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void ElevatedButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
@@ -244,21 +260,15 @@
}
public final class ButtonShapes {
- ctor public ButtonShapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape hoveredShape, androidx.compose.ui.graphics.Shape focusedShape, androidx.compose.ui.graphics.Shape checkedShape);
+ ctor public ButtonShapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
method public androidx.compose.ui.graphics.Shape component1();
method public androidx.compose.ui.graphics.Shape component2();
method public androidx.compose.ui.graphics.Shape component3();
- method public androidx.compose.ui.graphics.Shape component4();
- method public androidx.compose.ui.graphics.Shape component5();
- method public androidx.compose.material3.ButtonShapes copy(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape hoveredShape, androidx.compose.ui.graphics.Shape focusedShape, androidx.compose.ui.graphics.Shape checkedShape);
+ method public androidx.compose.material3.ButtonShapes copy(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
method public androidx.compose.ui.graphics.Shape getCheckedShape();
- method public androidx.compose.ui.graphics.Shape getFocusedShape();
- method public androidx.compose.ui.graphics.Shape getHoveredShape();
method public androidx.compose.ui.graphics.Shape getPressedShape();
method public androidx.compose.ui.graphics.Shape getShape();
property public final androidx.compose.ui.graphics.Shape checkedShape;
- property public final androidx.compose.ui.graphics.Shape focusedShape;
- property public final androidx.compose.ui.graphics.Shape hoveredShape;
property public final androidx.compose.ui.graphics.Shape pressedShape;
property public final androidx.compose.ui.graphics.Shape shape;
}
@@ -957,36 +967,72 @@
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors filledTonalIconToggleButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors filledTonalIconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFilledShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getLargeIconSize();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeRoundShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeSquareShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getMediumIconSize();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumRoundShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumSquareShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
- method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getRoundShape();
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long getSmallContainerSize();
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getSmallIconSize();
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long getSmallNarrowContainerSize();
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long getSmallWideContainerSize();
- method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSquareShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallRoundShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallSquareShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getStandardShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getXLargeIconSize();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeRoundShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeSquareShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getXSmallIconSize();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallRoundShape();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallSquareShape();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors iconToggleButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors iconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long largeContainerSize(optional int widthOption);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long mediumContainerSize(optional int widthOption);
method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke outlinedIconButtonBorder(boolean enabled);
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors outlinedIconButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors outlinedIconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke? outlinedIconToggleButtonBorder(boolean enabled, boolean checked);
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final long SmallContainerSize;
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float SmallIconSize;
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final long SmallNarrowContainerSize;
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final long SmallWideContainerSize;
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long smallContainerSize(optional int widthOption);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long xLargeContainerSize(optional int widthOption);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long xSmallContainerSize(optional int widthOption);
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape filledShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float largeIconSize;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeRoundShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeSquareShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float mediumIconSize;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumRoundShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumSquareShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedShape;
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape roundShape;
- property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape squareShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float smallIconSize;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallRoundShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallSquareShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape standardShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float xLargeIconSize;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeRoundShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeSquareShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float xSmallIconSize;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallRoundShape;
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallSquareShape;
field public static final androidx.compose.material3.IconButtonDefaults INSTANCE;
}
+ @kotlin.jvm.JvmInline public static final value class IconButtonDefaults.IconButtonWidthOption {
+ field public static final androidx.compose.material3.IconButtonDefaults.IconButtonWidthOption.Companion Companion;
+ }
+
+ public static final class IconButtonDefaults.IconButtonWidthOption.Companion {
+ method public int getNarrow();
+ method public int getUniform();
+ method public int getWide();
+ property public final int Narrow;
+ property public final int Uniform;
+ property public final int Wide;
+ }
+
public final class IconButtonKt {
method @androidx.compose.runtime.Composable public static void FilledIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void FilledIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
@@ -2516,18 +2562,12 @@
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getCheckedShape();
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedCheckedShape();
- method public androidx.compose.ui.graphics.Shape getElevatedFocusedShape();
- method public androidx.compose.ui.graphics.Shape getElevatedHoveredShape();
method public androidx.compose.ui.graphics.Shape getElevatedPressedShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedShape();
- method public androidx.compose.ui.graphics.Shape getFocusedShape();
- method public androidx.compose.ui.graphics.Shape getHoveredShape();
method public float getIconSize();
method public float getIconSpacing();
method public float getMinHeight();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedCheckedShape();
- method public androidx.compose.ui.graphics.Shape getOutlinedFocusedShape();
- method public androidx.compose.ui.graphics.Shape getOutlinedHoveredShape();
method public androidx.compose.ui.graphics.Shape getOutlinedPressedShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
method public androidx.compose.ui.graphics.Shape getPressedShape();
@@ -2535,13 +2575,11 @@
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSquareShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTonalCheckedShape();
- method public androidx.compose.ui.graphics.Shape getTonalFocusedShape();
- method public androidx.compose.ui.graphics.Shape getTonalHoveredShape();
method public androidx.compose.ui.graphics.Shape getTonalPressedShape();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTonalShape();
method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors outlinedToggleButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors outlinedToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
- method public androidx.compose.material3.ButtonShapes shapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape hoverShape, androidx.compose.ui.graphics.Shape focusShape, androidx.compose.ui.graphics.Shape checkedShape);
+ method public androidx.compose.material3.ButtonShapes shapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors toggleButtonColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors toggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors tonalToggleButtonColors();
@@ -2552,15 +2590,9 @@
property public final float MinHeight;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape checkedShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape elevatedCheckedShape;
- property public final androidx.compose.ui.graphics.Shape elevatedFocusedShape;
- property public final androidx.compose.ui.graphics.Shape elevatedHoveredShape;
property public final androidx.compose.ui.graphics.Shape elevatedPressedShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape elevatedShape;
- property public final androidx.compose.ui.graphics.Shape focusedShape;
- property public final androidx.compose.ui.graphics.Shape hoveredShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedCheckedShape;
- property public final androidx.compose.ui.graphics.Shape outlinedFocusedShape;
- property public final androidx.compose.ui.graphics.Shape outlinedHoveredShape;
property public final androidx.compose.ui.graphics.Shape outlinedPressedShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedShape;
property public final androidx.compose.ui.graphics.Shape pressedShape;
@@ -2568,8 +2600,6 @@
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape squareShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape tonalCheckedShape;
- property public final androidx.compose.ui.graphics.Shape tonalFocusedShape;
- property public final androidx.compose.ui.graphics.Shape tonalHoveredShape;
property public final androidx.compose.ui.graphics.Shape tonalPressedShape;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape tonalShape;
field public static final androidx.compose.material3.ToggleButtonDefaults INSTANCE;
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
index 815ed00..895f22c 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
@@ -117,6 +117,18 @@
examples = ButtonsExamples,
)
+private val ButtonGroups =
+ Component(
+ id = nextId(),
+ name = "Button Groups",
+ description =
+ "button groups is a container for material components that adds an animation on press",
+ guidelinesUrl = "$ComponentGuidelinesUrl/button-groups",
+ docsUrl = "$PackageSummaryUrl#buttongroups",
+ sourceUrl = "$Material3SourceUrl/ButtonGroup.kt",
+ examples = ButtonGroupsExamples,
+ )
+
private val Card =
Component(
id = nextId(),
@@ -556,6 +568,7 @@
BottomAppBars,
BottomSheets,
Buttons,
+ ButtonGroups,
Card,
Carousel,
Checkboxes,
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index 79e0183..61d227f 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -35,6 +35,7 @@
import androidx.compose.material3.samples.BasicAlertDialogSample
import androidx.compose.material3.samples.BottomAppBarWithFAB
import androidx.compose.material3.samples.BottomSheetScaffoldNestedScrollSample
+import androidx.compose.material3.samples.ButtonGroupSample
import androidx.compose.material3.samples.ButtonSample
import androidx.compose.material3.samples.ButtonWithIconSample
import androidx.compose.material3.samples.CardSample
@@ -108,6 +109,7 @@
import androidx.compose.material3.samples.LargeExtendedFloatingActionButtonSample
import androidx.compose.material3.samples.LargeExtendedFloatingActionButtonTextSample
import androidx.compose.material3.samples.LargeFloatingActionButtonSample
+import androidx.compose.material3.samples.LargeRoundUniformOutlinedIconButtonSample
import androidx.compose.material3.samples.LeadingIconTabs
import androidx.compose.material3.samples.LinearProgressIndicatorSample
import androidx.compose.material3.samples.LinearWavyProgressIndicatorSample
@@ -117,6 +119,7 @@
import androidx.compose.material3.samples.MediumExtendedFloatingActionButtonSample
import androidx.compose.material3.samples.MediumExtendedFloatingActionButtonTextSample
import androidx.compose.material3.samples.MediumFloatingActionButtonSample
+import androidx.compose.material3.samples.MediumRoundWideIconButtonSample
import androidx.compose.material3.samples.MenuSample
import androidx.compose.material3.samples.MenuWithScrollStateSample
import androidx.compose.material3.samples.ModalBottomSheetSample
@@ -229,6 +232,7 @@
import androidx.compose.material3.samples.WideNavigationRailCollapsedSample
import androidx.compose.material3.samples.WideNavigationRailExpandedSample
import androidx.compose.material3.samples.WideNavigationRailResponsiveSample
+import androidx.compose.material3.samples.XSmallNarrowSquareIconButtonsSample
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -369,6 +373,19 @@
}
)
+private const val ButtonGroupsExampleDescription = "ButtonGroup examples"
+private const val ButtonGroupsExampleSourceUrl = "$SampleSourceUrl/ButtonGroupSamples.kt"
+val ButtonGroupsExamples =
+ listOf(
+ Example(
+ name = "ButtonGroupSample",
+ description = ButtonGroupsExampleDescription,
+ sourceUrl = ButtonGroupsExampleSourceUrl,
+ ) {
+ ButtonGroupSample()
+ }
+ )
+
private const val CardsExampleDescription = "Cards examples"
private const val CardsExampleSourceUrl = "$SampleSourceUrl/CardSamples.kt"
val CardExamples =
@@ -1013,6 +1030,27 @@
sourceUrl = IconButtonExampleSourceUrl,
) {
OutlinedIconToggleButtonSample()
+ },
+ Example(
+ name = "XSmallNarrowSquareIconButtonsSample",
+ description = IconButtonExampleDescription,
+ sourceUrl = IconButtonExampleSourceUrl,
+ ) {
+ XSmallNarrowSquareIconButtonsSample()
+ },
+ Example(
+ name = "MediumRoundWideIconButtonSample",
+ description = IconButtonExampleDescription,
+ sourceUrl = IconButtonExampleSourceUrl,
+ ) {
+ MediumRoundWideIconButtonSample()
+ },
+ Example(
+ name = "LargeRoundUniformOutlinedIconButtonSample",
+ description = IconButtonExampleDescription,
+ sourceUrl = IconButtonExampleSourceUrl,
+ ) {
+ LargeRoundUniformOutlinedIconButtonSample()
}
)
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/IconButtonDemos.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/IconButtonDemos.kt
new file mode 100644
index 0000000..ef12156
--- /dev/null
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/IconButtonDemos.kt
@@ -0,0 +1,475 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.demos
+
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Lock
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.FilledIconButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.IconButtonDefaults.IconButtonWidthOption.Companion.Narrow
+import androidx.compose.material3.IconButtonDefaults.IconButtonWidthOption.Companion.Wide
+import androidx.compose.material3.OutlinedIconButton
+import androidx.compose.material3.Text
+import androidx.compose.material3.minimumInteractiveComponentSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun IconButtonMeasurementsDemo() {
+ val rowScrollState = rememberScrollState()
+ Row(modifier = Modifier.horizontalScroll(rowScrollState)) {
+ val columnScrollState = rememberScrollState()
+ val padding = 16.dp
+ Column(
+ modifier = Modifier.padding(horizontal = padding).verticalScroll(columnScrollState),
+ ) {
+ Spacer(modifier = Modifier.height(padding + 48.dp))
+ Text("XSmall", modifier = Modifier.height(48.dp + padding))
+ Text("Small", modifier = Modifier.height(48.dp + padding))
+ Text("Medium", modifier = Modifier.height(56.dp + padding))
+ Text("Large", modifier = Modifier.height(96.dp + padding))
+ Text("XLarge", modifier = Modifier.height(136.dp + padding))
+ }
+
+ // Default
+ Column(
+ modifier =
+ Modifier.padding(horizontal = padding)
+ .width(136.dp)
+ .verticalScroll(columnScrollState),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(padding)
+ ) {
+ Text("Default", modifier = Modifier.height(48.dp))
+ // XSmall uniform round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.xSmallContainerSize()),
+ shape = IconButtonDefaults.xSmallRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.xSmallIconSize)
+ )
+ }
+
+ // Small uniform round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.smallContainerSize()),
+ shape = IconButtonDefaults.smallRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.smallIconSize)
+ )
+ }
+
+ // Medium uniform round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.mediumContainerSize()),
+ shape = IconButtonDefaults.mediumRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.mediumIconSize)
+ )
+ }
+
+ // Large uniform round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.largeContainerSize()),
+ shape = IconButtonDefaults.largeRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.largeIconSize)
+ )
+ }
+
+ // XLarge uniform round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.xLargeContainerSize()),
+ shape = IconButtonDefaults.xLargeRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.xLargeIconSize)
+ )
+ }
+ }
+
+ // Narrow
+ Column(
+ modifier =
+ Modifier.padding(horizontal = padding)
+ .width(104.dp)
+ .verticalScroll(columnScrollState),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(padding)
+ ) {
+ Text("Narrow", modifier = Modifier.height(48.dp))
+
+ // XSmall narrow round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.xSmallContainerSize(Narrow)),
+ shape = IconButtonDefaults.xSmallRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.xSmallIconSize)
+ )
+ }
+
+ // Small narrow round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.smallContainerSize(Narrow)),
+ shape = IconButtonDefaults.smallRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.smallIconSize)
+ )
+ }
+
+ // Medium narrow round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.mediumContainerSize(Narrow)),
+ shape = IconButtonDefaults.mediumRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.mediumIconSize)
+ )
+ }
+
+ // Large narrow round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.largeContainerSize(Narrow)),
+ shape = IconButtonDefaults.largeRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.largeIconSize)
+ )
+ }
+
+ // XLarge narrow round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.xLargeContainerSize(Narrow)),
+ shape = IconButtonDefaults.xLargeRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.xLargeIconSize)
+ )
+ }
+ }
+
+ // Wide
+ Column(
+ modifier =
+ Modifier.padding(horizontal = padding)
+ .width(184.dp)
+ .verticalScroll(columnScrollState),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(padding)
+ ) {
+ Text("Wide", modifier = Modifier.height(48.dp))
+
+ // XSmall wide round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.xSmallContainerSize(Wide)),
+ shape = IconButtonDefaults.xSmallRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.xSmallIconSize)
+ )
+ }
+ // Small wide round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.smallContainerSize(Wide)),
+ shape = IconButtonDefaults.smallRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.smallIconSize)
+ )
+ }
+
+ // medium wide round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.mediumContainerSize(Wide)),
+ shape = IconButtonDefaults.mediumRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.mediumIconSize)
+ )
+ }
+
+ // Large wide round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.largeContainerSize(Wide)),
+ shape = IconButtonDefaults.largeRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.largeIconSize)
+ )
+ }
+
+ // XLarge wide round icon button
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.xLargeContainerSize(Wide)),
+ shape = IconButtonDefaults.xLargeRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.xLargeIconSize)
+ )
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun IconButtonCornerRadiusDemo() {
+ Column {
+ val rowScrollState = rememberScrollState()
+ val padding = 16.dp
+ // uniform round row
+ Row(
+ modifier =
+ Modifier.height(150.dp)
+ .horizontalScroll(rowScrollState)
+ .padding(horizontal = padding),
+ horizontalArrangement = Arrangement.spacedBy(padding),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ // xsmall round icon button
+ OutlinedIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier
+ // .minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.xSmallContainerSize()),
+ shape = IconButtonDefaults.xSmallRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.xSmallIconSize)
+ )
+ }
+
+ // Small round icon button
+ OutlinedIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier
+ // .minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.smallContainerSize()),
+ shape = IconButtonDefaults.smallRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.smallIconSize)
+ )
+ }
+
+ // Medium round icon button
+ OutlinedIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.mediumContainerSize()),
+ shape = IconButtonDefaults.mediumRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.mediumIconSize)
+ )
+ }
+
+ // Large uniform round icon button
+ OutlinedIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.largeContainerSize()),
+ shape = IconButtonDefaults.largeRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.largeIconSize)
+ )
+ }
+
+ // XLarge uniform round icon button
+ OutlinedIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.xLargeContainerSize()),
+ shape = IconButtonDefaults.xLargeRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.xLargeIconSize)
+ )
+ }
+ }
+
+ // uniform square row
+ Row(
+ modifier =
+ Modifier.height(150.dp)
+ .horizontalScroll(rowScrollState)
+ .padding(horizontal = padding),
+ horizontalArrangement = Arrangement.spacedBy(padding),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ // xsmall square icon button
+ OutlinedIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier
+ // .minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.xSmallContainerSize()),
+ shape = IconButtonDefaults.xSmallSquareShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.xSmallIconSize)
+ )
+ }
+
+ // Small round icon button
+ OutlinedIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier
+ // .minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.smallContainerSize()),
+ shape = IconButtonDefaults.smallSquareShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.smallIconSize)
+ )
+ }
+
+ // Medium round icon button
+ OutlinedIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.mediumContainerSize()),
+ shape = IconButtonDefaults.mediumSquareShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.mediumIconSize)
+ )
+ }
+
+ // Large uniform round icon button
+ OutlinedIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.largeContainerSize()),
+ shape = IconButtonDefaults.largeSquareShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.largeIconSize)
+ )
+ }
+
+ // XLarge uniform round icon button
+ OutlinedIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.xLargeContainerSize()),
+ shape = IconButtonDefaults.xLargeSquareShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.xLargeIconSize)
+ )
+ }
+ }
+ }
+}
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/Material3Demos.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/Material3Demos.kt
index 22b439f..bf06566 100644
--- a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/Material3Demos.kt
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/Material3Demos.kt
@@ -30,5 +30,12 @@
ComposableDemo("Swipe To Dismiss") { SwipeToDismissDemo() },
ComposableDemo("Tooltip") { TooltipDemo() },
ComposableDemo("Text fields") { MaterialTextFieldDemo() },
+ DemoCategory(
+ "Icon Buttons",
+ listOf(
+ ComposableDemo("Sizes") { IconButtonMeasurementsDemo() },
+ ComposableDemo("Corners") { IconButtonCornerRadiusDemo() }
+ )
+ ),
),
)
diff --git a/compose/material3/material3/lint-baseline.xml b/compose/material3/material3/lint-baseline.xml
index 57cf2ac..6270830 100644
--- a/compose/material3/material3/lint-baseline.xml
+++ b/compose/material3/material3/lint-baseline.xml
@@ -7,6 +7,51 @@
errorLine1=" Thread.sleep(300)"
errorLine2=" ~~~~~">
<location
+ file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt"/>
+ </issue>
+
+ <issue
+ id="BanThreadSleep"
+ message="Uses Thread.sleep()"
+ errorLine1=" Thread.sleep(300)"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt"/>
+ </issue>
+
+ <issue
+ id="BanThreadSleep"
+ message="Uses Thread.sleep()"
+ errorLine1=" Thread.sleep(300)"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt"/>
+ </issue>
+
+ <issue
+ id="BanThreadSleep"
+ message="Uses Thread.sleep()"
+ errorLine1=" Thread.sleep(300)"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt"/>
+ </issue>
+
+ <issue
+ id="BanThreadSleep"
+ message="Uses Thread.sleep()"
+ errorLine1=" Thread.sleep(300)"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt"/>
+ </issue>
+
+ <issue
+ id="BanThreadSleep"
+ message="Uses Thread.sleep()"
+ errorLine1=" Thread.sleep(300)"
+ errorLine2=" ~~~~~">
+ <location
file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/CardScreenshotTest.kt"/>
</issue>
@@ -245,24 +290,6 @@
</issue>
<issue
- id="BanThreadSleep"
- message="Uses Thread.sleep()"
- errorLine1=" Thread.sleep(300)"
- errorLine2=" ~~~~~">
- <location
- file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/ToggleButtonScreenshotTest.kt"/>
- </issue>
-
- <issue
- id="BanThreadSleep"
- message="Uses Thread.sleep()"
- errorLine1=" Thread.sleep(300)"
- errorLine2=" ~~~~~">
- <location
- file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/ToggleButtonScreenshotTest.kt"/>
- </issue>
-
- <issue
id="PrimitiveInCollection"
message="variable crossAxisSizes with type List<Integer>: replace with IntList"
errorLine1=" val crossAxisSizes = mutableListOf<Int>()"
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ButtonGroupSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ButtonGroupSamples.kt
new file mode 100644
index 0000000..6ba6bd5
--- /dev/null
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ButtonGroupSamples.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonGroup
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Preview
+@Sampled
+@Composable
+fun ButtonGroupSample() {
+ ButtonGroup {
+ Button(modifier = Modifier.weight(1.5f), onClick = {}) { Text("A") }
+ Button(modifier = Modifier.weight(1f), onClick = {}) { Text("B") }
+ Button(modifier = Modifier.width(90.dp), onClick = {}) { Text("C") }
+ Button(modifier = Modifier.weight(1f), onClick = {}) { Text("D") }
+ }
+}
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt
index fac8536..f159640 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt
@@ -21,7 +21,6 @@
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.outlined.Lock
-import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.FilledIconToggleButton
@@ -33,6 +32,7 @@
import androidx.compose.material3.IconToggleButton
import androidx.compose.material3.OutlinedIconButton
import androidx.compose.material3.OutlinedIconToggleButton
+import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -56,16 +56,23 @@
@Preview
@Sampled
@Composable
-fun SmallSquareNarrowIconButtonSample() {
- IconButton(
+fun XSmallNarrowSquareIconButtonsSample() {
+ // Small narrow round icon button
+ FilledIconButton(
onClick = { /* doSomething() */ },
- modifier = Modifier.size(IconButtonDefaults.SmallNarrowContainerSize),
- shape = IconButtonDefaults.squareShape
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(
+ IconButtonDefaults.xSmallContainerSize(
+ IconButtonDefaults.IconButtonWidthOption.Narrow
+ )
+ ),
+ shape = IconButtonDefaults.xSmallSquareShape
) {
Icon(
Icons.Outlined.Lock,
contentDescription = "Localized description",
- modifier = Modifier.size(IconButtonDefaults.SmallIconSize)
+ modifier = Modifier.size(IconButtonDefaults.xSmallIconSize)
)
}
}
@@ -74,21 +81,43 @@
@Preview
@Sampled
@Composable
-fun SmallRoundWideIconButtonSample() {
+fun MediumRoundWideIconButtonSample() {
IconButton(
onClick = { /* doSomething() */ },
- modifier = Modifier.size(IconButtonDefaults.SmallWideContainerSize),
- shape = IconButtonDefaults.roundShape
+ modifier =
+ Modifier.size(
+ IconButtonDefaults.mediumContainerSize(
+ IconButtonDefaults.IconButtonWidthOption.Wide
+ )
+ ),
+ shape = IconButtonDefaults.mediumRoundShape
) {
Icon(
Icons.Outlined.Lock,
contentDescription = "Localized description",
- modifier = Modifier.size(IconButtonDefaults.SmallIconSize)
+ modifier = Modifier.size(IconButtonDefaults.mediumIconSize)
)
}
}
-@OptIn(ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Preview
+@Sampled
+@Composable
+fun LargeRoundUniformOutlinedIconButtonSample() {
+ OutlinedIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(IconButtonDefaults.largeContainerSize()),
+ shape = IconButtonDefaults.largeRoundShape
+ ) {
+ Icon(
+ Icons.Outlined.Lock,
+ contentDescription = "Localized description",
+ modifier = Modifier.size(IconButtonDefaults.largeIconSize)
+ )
+ }
+}
+
@Preview
@Sampled
@Composable
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ToggleButtonSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ToggleButtonSamples.kt
index 8460484..b215df5 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ToggleButtonSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ToggleButtonSamples.kt
@@ -57,8 +57,6 @@
ButtonShapes(
shape = ToggleButtonDefaults.roundShape,
pressedShape = ToggleButtonDefaults.pressedShape,
- hoveredShape = ToggleButtonDefaults.hoveredShape,
- focusedShape = ToggleButtonDefaults.focusedShape,
checkedShape = ToggleButtonDefaults.squareShape
)
ToggleButton(checked = checked, onCheckedChange = { checked = it }, shapes = shapes) {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt
new file mode 100644
index 0000000..43f5f50
--- /dev/null
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import android.os.Build
+import androidx.compose.foundation.layout.Box
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O, maxSdkVersion = 32)
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+class ButtonGroupScreenshotTest {
+ @get:Rule val rule = createComposeRule()
+
+ @get:Rule val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL3)
+
+ private val wrapperTestTag = "WrapperTestTag"
+ private val aButton = "AButton"
+ private val bButton = "BButton"
+ private val cButton = "CButton"
+ private val dButton = "DButton"
+ private val eButton = "EButton"
+
+ @Test
+ fun buttonGroup_lightTheme() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup {
+ Button(onClick = {}) { Text("A") }
+ Button(onClick = {}) { Text("B") }
+ Button(onClick = {}) { Text("C") }
+ Button(onClick = {}) { Text("D") }
+ Button(onClick = {}) { Text("E") }
+ }
+ }
+ }
+
+ assertAgainstGolden("buttonGroup_lightTheme")
+ }
+
+ @Test
+ fun buttonGroup_darkTheme() {
+ rule.setMaterialContent(darkColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup {
+ Button(onClick = {}) { Text("A") }
+ Button(onClick = {}) { Text("B") }
+ Button(onClick = {}) { Text("C") }
+ Button(onClick = {}) { Text("D") }
+ Button(onClick = {}) { Text("E") }
+ }
+ }
+ }
+
+ assertAgainstGolden("buttonGroup_darkTheme")
+ }
+
+ @Ignore
+ @Test
+ fun buttonGroup_firstPressed_lightTheme() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup {
+ Button(modifier = Modifier.testTag(aButton), onClick = {}) { Text("A") }
+ Button(modifier = Modifier.testTag(bButton), onClick = {}) { Text("B") }
+ Button(modifier = Modifier.testTag(cButton), onClick = {}) { Text("C") }
+ Button(modifier = Modifier.testTag(dButton), onClick = {}) { Text("D") }
+ Button(modifier = Modifier.testTag(eButton), onClick = {}) { Text("E") }
+ }
+ }
+ }
+
+ rule.mainClock.autoAdvance = false
+ rule.onNodeWithTag(aButton).performTouchInput { down(center) }
+
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle() // Wait for measure
+ rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+ // Ripples are drawn on the RenderThread, not the main (UI) thread, so we can't wait for
+ // synchronization. Instead just wait until after the ripples are finished animating.
+ Thread.sleep(300)
+
+ assertAgainstGolden("buttonGroup_firstPressed_lightTheme")
+ }
+
+ @Ignore
+ @Test
+ fun buttonGroup_secondPressed_lightTheme() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup {
+ Button(modifier = Modifier.testTag(aButton), onClick = {}) { Text("A") }
+ Button(modifier = Modifier.testTag(bButton), onClick = {}) { Text("B") }
+ Button(modifier = Modifier.testTag(cButton), onClick = {}) { Text("C") }
+ Button(modifier = Modifier.testTag(dButton), onClick = {}) { Text("D") }
+ Button(modifier = Modifier.testTag(eButton), onClick = {}) { Text("E") }
+ }
+ }
+ }
+
+ rule.mainClock.autoAdvance = false
+ rule.onNodeWithTag(bButton).performTouchInput { down(center) }
+
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle() // Wait for measure
+ rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+ // Ripples are drawn on the RenderThread, not the main (UI) thread, so we can't wait for
+ // synchronization. Instead just wait until after the ripples are finished animating.
+ Thread.sleep(300)
+
+ assertAgainstGolden("buttonGroup_secondPressed_lightTheme")
+ }
+
+ @Ignore
+ @Test
+ fun buttonGroup_thirdPressed_lightTheme() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup {
+ Button(modifier = Modifier.testTag(aButton), onClick = {}) { Text("A") }
+ Button(modifier = Modifier.testTag(bButton), onClick = {}) { Text("B") }
+ Button(modifier = Modifier.testTag(cButton), onClick = {}) { Text("C") }
+ Button(modifier = Modifier.testTag(dButton), onClick = {}) { Text("D") }
+ Button(modifier = Modifier.testTag(eButton), onClick = {}) { Text("E") }
+ }
+ }
+ }
+
+ rule.mainClock.autoAdvance = false
+ rule.onNodeWithTag(cButton).performTouchInput { down(center) }
+
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle() // Wait for measure
+ rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+ // Ripples are drawn on the RenderThread, not the main (UI) thread, so we can't wait for
+ // synchronization. Instead just wait until after the ripples are finished animating.
+ Thread.sleep(300)
+
+ assertAgainstGolden("buttonGroup_thirdPressed_lightTheme")
+ }
+
+ @Ignore
+ @Test
+ fun buttonGroup_fourthPressed_lightTheme() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup {
+ Button(modifier = Modifier.testTag(aButton), onClick = {}) { Text("A") }
+ Button(modifier = Modifier.testTag(bButton), onClick = {}) { Text("B") }
+ Button(modifier = Modifier.testTag(cButton), onClick = {}) { Text("C") }
+ Button(modifier = Modifier.testTag(dButton), onClick = {}) { Text("D") }
+ Button(modifier = Modifier.testTag(eButton), onClick = {}) { Text("E") }
+ }
+ }
+ }
+
+ rule.mainClock.autoAdvance = false
+ rule.onNodeWithTag(dButton).performTouchInput { down(center) }
+
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle() // Wait for measure
+ rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+ // Ripples are drawn on the RenderThread, not the main (UI) thread, so we can't wait for
+ // synchronization. Instead just wait until after the ripples are finished animating.
+ Thread.sleep(300)
+
+ assertAgainstGolden("buttonGroup_fourthPressed_lightTheme")
+ }
+
+ @Ignore
+ @Test
+ fun buttonGroup_fifthPressed_lightTheme() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup {
+ Button(modifier = Modifier.testTag(aButton), onClick = {}) { Text("A") }
+ Button(modifier = Modifier.testTag(bButton), onClick = {}) { Text("B") }
+ Button(modifier = Modifier.testTag(cButton), onClick = {}) { Text("C") }
+ Button(modifier = Modifier.testTag(dButton), onClick = {}) { Text("D") }
+ Button(modifier = Modifier.testTag(eButton), onClick = {}) { Text("E") }
+ }
+ }
+ }
+
+ rule.mainClock.autoAdvance = false
+ rule.onNodeWithTag(eButton).performTouchInput { down(center) }
+
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle() // Wait for measure
+ rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+ // Ripples are drawn on the RenderThread, not the main (UI) thread, so we can't wait for
+ // synchronization. Instead just wait until after the ripples are finished animating.
+ Thread.sleep(300)
+
+ assertAgainstGolden("buttonGroup_fifthPressed_lightTheme")
+ }
+
+ private fun assertAgainstGolden(goldenName: String) {
+ rule
+ .onNodeWithTag(wrapperTestTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, goldenName)
+ }
+}
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupTest.kt
new file mode 100644
index 0000000..fb0b3e3
--- /dev/null
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupTest.kt
@@ -0,0 +1,468 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.width
+import androidx.compose.testutils.assertIsEqualTo
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+class ButtonGroupTest {
+ @get:Rule val rule = createComposeRule()
+
+ private val wrapperTestTag = "WrapperTestTag"
+ private val aButton = "AButton"
+ private val bButton = "BButton"
+ private val cButton = "CButton"
+ private val dButton = "DButton"
+
+ @Test
+ fun default_positioning() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup {
+ Button(modifier = Modifier.testTag(aButton), onClick = {}) { Text("A") }
+ Button(modifier = Modifier.testTag(bButton), onClick = {}) { Text("B") }
+ Button(modifier = Modifier.testTag(cButton), onClick = {}) { Text("C") }
+ Button(modifier = Modifier.testTag(dButton), onClick = {}) { Text("D") }
+ }
+ }
+ }
+
+ val wrapperBounds = rule.onNodeWithTag(wrapperTestTag).getUnclippedBoundsInRoot()
+ val aButtonBounds = rule.onNodeWithTag(aButton).getUnclippedBoundsInRoot()
+ val bButtonBounds = rule.onNodeWithTag(bButton).getUnclippedBoundsInRoot()
+ val cButtonBounds = rule.onNodeWithTag(cButton).getUnclippedBoundsInRoot()
+ val dButtonBounds = rule.onNodeWithTag(dButton).getUnclippedBoundsInRoot()
+
+ (aButtonBounds.left - wrapperBounds.left).assertIsEqualTo(0.dp)
+ (bButtonBounds.left - aButtonBounds.right).assertIsEqualTo(12.dp)
+ (cButtonBounds.left - bButtonBounds.right).assertIsEqualTo(12.dp)
+ (dButtonBounds.left - cButtonBounds.right).assertIsEqualTo(12.dp)
+ (wrapperBounds.right - dButtonBounds.right).assertIsEqualTo(0.dp)
+ }
+
+ @Test
+ fun differentHorizontalSpacing_positioning() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+ Button(modifier = Modifier.testTag(aButton), onClick = {}) { Text("A") }
+ Button(modifier = Modifier.testTag(bButton), onClick = {}) { Text("B") }
+ Button(modifier = Modifier.testTag(cButton), onClick = {}) { Text("C") }
+ Button(modifier = Modifier.testTag(dButton), onClick = {}) { Text("D") }
+ }
+ }
+ }
+
+ val wrapperBounds = rule.onNodeWithTag(wrapperTestTag).getUnclippedBoundsInRoot()
+ val aButtonBounds = rule.onNodeWithTag(aButton).getUnclippedBoundsInRoot()
+ val bButtonBounds = rule.onNodeWithTag(bButton).getUnclippedBoundsInRoot()
+ val cButtonBounds = rule.onNodeWithTag(cButton).getUnclippedBoundsInRoot()
+ val dButtonBounds = rule.onNodeWithTag(dButton).getUnclippedBoundsInRoot()
+
+ (aButtonBounds.left - wrapperBounds.left).assertIsEqualTo(0.dp)
+ (bButtonBounds.left - aButtonBounds.right).assertIsEqualTo(4.dp)
+ (cButtonBounds.left - bButtonBounds.right).assertIsEqualTo(4.dp)
+ (dButtonBounds.left - cButtonBounds.right).assertIsEqualTo(4.dp)
+ (wrapperBounds.right - dButtonBounds.right).assertIsEqualTo(0.dp)
+ }
+
+ @Test
+ fun default_firstPressed_buttonSizing() {
+ val width = 75.dp
+ val animateFraction = 0.15f
+ val expectedExpandWidth = width + (width * animateFraction)
+ val expectedCompressWidth = width - (width * animateFraction)
+
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup {
+ Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+ Text("A")
+ }
+ Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+ Text("B")
+ }
+ Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+ Text("C")
+ }
+ Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+ Text("D")
+ }
+ }
+ }
+ }
+
+ rule.mainClock.autoAdvance = false
+ rule.onNodeWithTag(aButton).performTouchInput { down(center) }
+
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle() // Wait for measure
+ rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+ rule.waitForIdle()
+
+ val aButton = rule.onNodeWithTag(aButton)
+ val bButton = rule.onNodeWithTag(bButton)
+ val cButton = rule.onNodeWithTag(cButton)
+ val dButton = rule.onNodeWithTag(dButton)
+
+ aButton.assertWidthIsEqualTo(expectedExpandWidth)
+ bButton.assertWidthIsEqualTo(expectedCompressWidth)
+ cButton.assertWidthIsEqualTo(width)
+ dButton.assertWidthIsEqualTo(width)
+ }
+
+ @Test
+ fun default_secondPressed_buttonSizing() {
+ val width = 75.dp
+ val animateFraction = 0.15f
+ val expectedExpandWidth = width + (width * animateFraction)
+ val expectedCompressWidth = width - (width * (animateFraction / 2f))
+
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup {
+ Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+ Text("A")
+ }
+ Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+ Text("B")
+ }
+ Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+ Text("C")
+ }
+ Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+ Text("D")
+ }
+ }
+ }
+ }
+
+ rule.mainClock.autoAdvance = false
+ rule.onNodeWithTag(bButton).performTouchInput { down(center) }
+
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle() // Wait for measure
+ rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+ rule.waitForIdle()
+
+ val aButton = rule.onNodeWithTag(aButton)
+ val bButton = rule.onNodeWithTag(bButton)
+ val cButton = rule.onNodeWithTag(cButton)
+ val dButton = rule.onNodeWithTag(dButton)
+
+ aButton.assertWidthIsEqualTo(expectedCompressWidth)
+ bButton.assertWidthIsEqualTo(expectedExpandWidth)
+ cButton.assertWidthIsEqualTo(expectedCompressWidth)
+ dButton.assertWidthIsEqualTo(width)
+ }
+
+ @Test
+ fun default_thirdPressed_buttonSizing() {
+ val width = 75.dp
+ val animateFraction = 0.15f
+ val expectedExpandWidth = width + (width * animateFraction)
+ val expectedCompressWidth = width - (width * (animateFraction / 2f))
+
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup {
+ Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+ Text("A")
+ }
+ Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+ Text("B")
+ }
+ Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+ Text("C")
+ }
+ Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+ Text("D")
+ }
+ }
+ }
+ }
+
+ rule.mainClock.autoAdvance = false
+ rule.onNodeWithTag(cButton).performTouchInput { down(center) }
+
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle() // Wait for measure
+ rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+ rule.waitForIdle()
+
+ val aButton = rule.onNodeWithTag(aButton)
+ val bButton = rule.onNodeWithTag(bButton)
+ val cButton = rule.onNodeWithTag(cButton)
+ val dButton = rule.onNodeWithTag(dButton)
+
+ aButton.assertWidthIsEqualTo(width)
+ bButton.assertWidthIsEqualTo(expectedCompressWidth)
+ cButton.assertWidthIsEqualTo(expectedExpandWidth)
+ dButton.assertWidthIsEqualTo(expectedCompressWidth)
+ }
+
+ @Test
+ fun default_fourthPressed_buttonSizing() {
+ val width = 75.dp
+ val animateFraction = 0.15f
+ val expectedExpandWidth = width + (width * animateFraction)
+ val expectedCompressWidth = width - (width * animateFraction)
+
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup {
+ Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+ Text("A")
+ }
+ Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+ Text("B")
+ }
+ Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+ Text("C")
+ }
+ Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+ Text("D")
+ }
+ }
+ }
+ }
+
+ rule.mainClock.autoAdvance = false
+ rule.onNodeWithTag(dButton).performTouchInput { down(center) }
+
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle() // Wait for measure
+ rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+ rule.waitForIdle()
+
+ val aButton = rule.onNodeWithTag(aButton)
+ val bButton = rule.onNodeWithTag(bButton)
+ val cButton = rule.onNodeWithTag(cButton)
+ val dButton = rule.onNodeWithTag(dButton)
+
+ aButton.assertWidthIsEqualTo(width)
+ bButton.assertWidthIsEqualTo(width)
+ cButton.assertWidthIsEqualTo(expectedCompressWidth)
+ dButton.assertWidthIsEqualTo(expectedExpandWidth)
+ }
+
+ @Test
+ fun customAnimateFraction_firstPressed_buttonSizing() {
+ val width = 75.dp
+ val animateFraction = 0.3f
+ val expectedExpandWidth = width + (width * animateFraction)
+ val expectedCompressWidth = width - (width * animateFraction)
+
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup(animateFraction = animateFraction) {
+ Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+ Text("A")
+ }
+ Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+ Text("B")
+ }
+ Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+ Text("C")
+ }
+ Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+ Text("D")
+ }
+ }
+ }
+ }
+
+ rule.mainClock.autoAdvance = false
+ rule.onNodeWithTag(aButton).performTouchInput { down(center) }
+
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle() // Wait for measure
+ rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+ rule.waitForIdle()
+
+ val aButton = rule.onNodeWithTag(aButton)
+ val bButton = rule.onNodeWithTag(bButton)
+ val cButton = rule.onNodeWithTag(cButton)
+ val dButton = rule.onNodeWithTag(dButton)
+
+ aButton.assertWidthIsEqualTo(expectedExpandWidth)
+ bButton.assertWidthIsEqualTo(expectedCompressWidth)
+ cButton.assertWidthIsEqualTo(width)
+ dButton.assertWidthIsEqualTo(width)
+ }
+
+ @Test
+ fun customAnimateFraction_secondPressed_buttonSizing() {
+ val width = 75.dp
+ val animateFraction = 0.3f
+ val expectedExpandWidth = width + (width * animateFraction)
+ val expectedCompressWidth = width - (width * animateFraction / 2f)
+
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup(animateFraction = animateFraction) {
+ Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+ Text("A")
+ }
+ Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+ Text("B")
+ }
+ Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+ Text("C")
+ }
+ Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+ Text("D")
+ }
+ }
+ }
+ }
+
+ rule.mainClock.autoAdvance = false
+ rule.onNodeWithTag(bButton).performTouchInput { down(center) }
+
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle() // Wait for measure
+ rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+ rule.waitForIdle()
+
+ val aButton = rule.onNodeWithTag(aButton)
+ val bButton = rule.onNodeWithTag(bButton)
+ val cButton = rule.onNodeWithTag(cButton)
+ val dButton = rule.onNodeWithTag(dButton)
+
+ aButton.assertWidthIsEqualTo(expectedCompressWidth)
+ bButton.assertWidthIsEqualTo(expectedExpandWidth)
+ cButton.assertWidthIsEqualTo(expectedCompressWidth)
+ dButton.assertWidthIsEqualTo(width)
+ }
+
+ @Test
+ fun customAnimateFraction_thirdPressed_buttonSizing() {
+ val width = 75.dp
+ val animateFraction = 0.3f
+ val expectedExpandWidth = width + (width * animateFraction)
+ val expectedCompressWidth = width - (width * animateFraction / 2f)
+
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup(animateFraction = animateFraction) {
+ Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+ Text("A")
+ }
+ Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+ Text("B")
+ }
+ Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+ Text("C")
+ }
+ Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+ Text("D")
+ }
+ }
+ }
+ }
+
+ rule.mainClock.autoAdvance = false
+ rule.onNodeWithTag(cButton).performTouchInput { down(center) }
+
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle() // Wait for measure
+ rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+ rule.waitForIdle()
+
+ val aButton = rule.onNodeWithTag(aButton)
+ val bButton = rule.onNodeWithTag(bButton)
+ val cButton = rule.onNodeWithTag(cButton)
+ val dButton = rule.onNodeWithTag(dButton)
+
+ aButton.assertWidthIsEqualTo(width)
+ bButton.assertWidthIsEqualTo(expectedCompressWidth)
+ cButton.assertWidthIsEqualTo(expectedExpandWidth)
+ dButton.assertWidthIsEqualTo(expectedCompressWidth)
+ }
+
+ @Test
+ fun customAnimateFraction_fourthPressed_buttonSizing() {
+ val width = 75.dp
+ val animateFraction = 0.3f
+ val expectedExpandWidth = width + (width * animateFraction)
+ val expectedCompressWidth = width - (width * animateFraction)
+
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(Modifier.testTag(wrapperTestTag)) {
+ ButtonGroup(animateFraction = animateFraction) {
+ Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+ Text("A")
+ }
+ Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+ Text("B")
+ }
+ Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+ Text("C")
+ }
+ Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+ Text("D")
+ }
+ }
+ }
+ }
+
+ rule.mainClock.autoAdvance = false
+ rule.onNodeWithTag(dButton).performTouchInput { down(center) }
+
+ rule.mainClock.advanceTimeByFrame()
+ rule.waitForIdle() // Wait for measure
+ rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+ rule.waitForIdle()
+
+ val aButton = rule.onNodeWithTag(aButton)
+ val bButton = rule.onNodeWithTag(bButton)
+ val cButton = rule.onNodeWithTag(cButton)
+ val dButton = rule.onNodeWithTag(dButton)
+
+ aButton.assertWidthIsEqualTo(width)
+ bButton.assertWidthIsEqualTo(width)
+ cButton.assertWidthIsEqualTo(expectedCompressWidth)
+ dButton.assertWidthIsEqualTo(expectedExpandWidth)
+ }
+}
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
index 2a8c1db..99e18f1 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
@@ -20,6 +20,7 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.outlined.FavoriteBorder
@@ -27,6 +28,7 @@
import androidx.compose.material3.tokens.FilledTonalIconButtonTokens
import androidx.compose.material3.tokens.IconButtonTokens
import androidx.compose.material3.tokens.OutlinedIconButtonTokens
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -37,6 +39,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.SemanticsProperties
@@ -62,16 +65,19 @@
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import com.google.common.truth.Truth
+import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@LargeTest
@RunWith(AndroidJUnit4::class)
/** Tests for icon buttons. */
@@ -79,7 +85,93 @@
@get:Rule val rule = createComposeRule()
@Test
- fun iconButton_size() {
+ fun iconButton_xsmall_visualBounds() {
+ val expectedWidth =
+ with(rule.density) { IconButtonDefaults.xSmallContainerSize().width.roundToPx() }
+ val expectedHeight =
+ with(rule.density) { IconButtonDefaults.xSmallContainerSize().height.roundToPx() }
+ val expectedSize = IntSize(expectedWidth, expectedHeight)
+
+ assertVisualBounds(
+ {
+ IconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.xSmallContainerSize())
+ .testTag(IconButtonTestTag),
+ shape = IconButtonDefaults.smallRoundShape
+ ) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ },
+ expectedSize
+ )
+ }
+
+ @Test
+ fun iconButton_xSmall_semantic_bounds() {
+ rule
+ .setMaterialContentForSizeAssertions {
+ IconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.xSmallContainerSize())
+ ) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ }
+ .assertTouchWidthIsEqualTo(IconButtonAccessibilitySize)
+ .assertTouchHeightIsEqualTo(IconButtonAccessibilitySize)
+ }
+
+ @Test
+ fun iconButton_small_visualBounds() {
+ val expectedWidth =
+ with(rule.density) { IconButtonDefaults.smallContainerSize().width.roundToPx() }
+ val expectedHeight =
+ with(rule.density) { IconButtonDefaults.smallContainerSize().height.roundToPx() }
+ val expectedSize = IntSize(expectedWidth, expectedHeight)
+
+ val size = IconButtonDefaults.smallContainerSize()
+
+ assertVisualBounds(
+ {
+ IconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(size)
+ .testTag(IconButtonTestTag),
+ shape = IconButtonDefaults.smallRoundShape
+ ) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ },
+ expectedSize
+ )
+ }
+
+ @Test
+ fun iconButton_small_semantic_bounds() {
+ rule
+ .setMaterialContentForSizeAssertions {
+ IconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.smallContainerSize())
+ ) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ }
+ .assertTouchWidthIsEqualTo(IconButtonAccessibilitySize)
+ .assertTouchHeightIsEqualTo(IconButtonAccessibilitySize)
+ }
+
+ @Test
+ fun iconButton_medium_size() {
rule
.setMaterialContentForSizeAssertions {
IconButton(onClick = { /* doSomething() */ }) {
@@ -92,42 +184,38 @@
.assertTouchHeightIsEqualTo(IconButtonAccessibilitySize)
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
@Test
- fun iconButton_wideShape() {
- val shape = ShapeDefaults.Medium
- val background = Color.Yellow
- val iconButtonColor = Color.Blue
- rule.setMaterialContent(lightColorScheme()) {
- Surface(color = background) {
- Box {
- IconButton(
- onClick = { /* doSomething() */ },
- modifier =
- Modifier.semantics(mergeDescendants = true) {}
- .testTag(IconTestTag)
- .size(50.dp),
- shape = shape,
- colors =
- IconButtonDefaults.iconButtonColors(containerColor = iconButtonColor)
- ) {}
+ fun iconButton_large_size() {
+ var size = DpSize.Zero
+ rule
+ .setMaterialContentForSizeAssertions {
+ size = IconButtonDefaults.largeContainerSize()
+ IconButton(onClick = { /* doSomething() */ }, modifier = Modifier.size(size)) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
}
}
- }
-
- rule
- .onNodeWithTag(IconTestTag)
- .captureToImage()
- .assertShape(
- density = rule.density,
- shape = shape,
- shapeColor = iconButtonColor,
- backgroundColor = background,
- shapeOverlapPixelCount = with(rule.density) { 1.dp.toPx() }
- )
+ .assertWidthIsEqualTo(size.width)
+ .assertHeightIsEqualTo(size.height)
+ .assertTouchWidthIsEqualTo(size.width)
+ .assertTouchHeightIsEqualTo(size.height)
}
- @OptIn(ExperimentalMaterial3Api::class)
+ @Test
+ fun iconButton_xlarge_size() {
+ var size = DpSize.Zero
+ rule
+ .setMaterialContentForSizeAssertions {
+ size = IconButtonDefaults.xLargeContainerSize()
+ IconButton(onClick = { /* doSomething() */ }, modifier = Modifier.size(size)) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ }
+ .assertWidthIsEqualTo(size.width)
+ .assertHeightIsEqualTo(size.height)
+ .assertTouchWidthIsEqualTo(size.width)
+ .assertTouchHeightIsEqualTo(size.height)
+ }
+
@Test
fun iconButton_sizeWithoutMinTargetEnforcement() {
rule
@@ -390,7 +478,6 @@
@Test
fun iconToggleButton_clickInMinimumTouchTarget(): Unit =
with(rule.density) {
- val tag = "iconToggleButton"
var checked by mutableStateOf(false)
rule.setMaterialContent(lightColorScheme()) {
// Box is needed because otherwise the control will be expanded to fill its parent
@@ -398,14 +485,17 @@
IconToggleButton(
checked = checked,
onCheckedChange = { checked = it },
- modifier = Modifier.align(Alignment.Center).requiredSize(2.dp).testTag(tag)
+ modifier =
+ Modifier.align(Alignment.Center)
+ .requiredSize(2.dp)
+ .testTag(IconButtonTestTag)
) {
Box(Modifier.size(2.dp))
}
}
}
rule
- .onNodeWithTag(tag)
+ .onNodeWithTag(IconButtonTestTag)
.assertIsOff()
.assertWidthIsEqualTo(2.dp)
.assertHeightIsEqualTo(2.dp)
@@ -435,7 +525,98 @@
}
@Test
- fun filledIconButton_size() {
+ fun filledIconButton_xsmall_visualBounds() {
+ val expectedWidth =
+ with(rule.density) { IconButtonDefaults.xSmallContainerSize().width.roundToPx() }
+ val expectedHeight =
+ with(rule.density) { IconButtonDefaults.xSmallContainerSize().height.roundToPx() }
+ val expectedSize = IntSize(expectedWidth, expectedHeight)
+
+ // The bounds of a testTag on a box that contains the progress indicator are not affected
+ // by the padding added on the layout of the progress bar.
+ assertVisualBounds(
+ {
+ val size = IconButtonDefaults.xSmallContainerSize()
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(size)
+ .testTag(IconButtonTestTag),
+ shape = IconButtonDefaults.xSmallRoundShape
+ ) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ },
+ expectedSize
+ )
+ }
+
+ @Test
+ fun filledIconButton_xSmall_semantic_bounds() {
+ rule
+ .setMaterialContentForSizeAssertions {
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.xSmallContainerSize()),
+ shape = IconButtonDefaults.xSmallRoundShape
+ ) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ }
+ .assertTouchWidthIsEqualTo(IconButtonAccessibilitySize)
+ .assertTouchHeightIsEqualTo(IconButtonAccessibilitySize)
+ }
+
+ @Test
+ fun filledIconButton_small_visualBounds() {
+ val expectedWidth =
+ with(rule.density) { IconButtonDefaults.smallContainerSize().width.roundToPx() }
+ val expectedHeight =
+ with(rule.density) { IconButtonDefaults.smallContainerSize().height.roundToPx() }
+ val expectedSize = IntSize(expectedWidth, expectedHeight)
+
+ // The bounds of a testTag on a box that contains the progress indicator are not affected
+ // by the padding added on the layout of the progress bar.
+ assertVisualBounds(
+ {
+ val size = IconButtonDefaults.smallContainerSize()
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(size)
+ .testTag(IconButtonTestTag),
+ shape = IconButtonDefaults.smallRoundShape
+ ) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ },
+ expectedSize
+ )
+ }
+
+ @Test
+ fun filledIconButton_small_semantic_bounds() {
+ rule
+ .setMaterialContentForSizeAssertions {
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.minimumInteractiveComponentSize()
+ .size(IconButtonDefaults.smallContainerSize())
+ ) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ }
+ .assertTouchWidthIsEqualTo(IconButtonAccessibilitySize)
+ .assertTouchHeightIsEqualTo(IconButtonAccessibilitySize)
+ }
+
+ @Test
+ fun filledIconButton_medium_size() {
rule
.setMaterialContentForSizeAssertions {
FilledIconButton(onClick = { /* doSomething() */ }) {
@@ -449,6 +630,80 @@
}
@Test
+ fun filledIconButton_large_size() {
+ var size = DpSize.Zero
+ rule
+ .setMaterialContentForSizeAssertions {
+ size = IconButtonDefaults.largeContainerSize()
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(size)
+ ) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ }
+ .assertWidthIsEqualTo(size.width)
+ .assertHeightIsEqualTo(size.height)
+ .assertTouchWidthIsEqualTo(size.width)
+ .assertTouchHeightIsEqualTo(size.height)
+ }
+
+ @Test
+ fun filledIconButton_xlarge_size() {
+ var size = DpSize.Zero
+ rule
+ .setMaterialContentForSizeAssertions {
+ size = IconButtonDefaults.xLargeContainerSize()
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier = Modifier.size(size)
+ ) {
+ Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+ }
+ }
+ .assertWidthIsEqualTo(size.width)
+ .assertHeightIsEqualTo(size.height)
+ .assertTouchWidthIsEqualTo(size.width)
+ .assertTouchHeightIsEqualTo(size.height)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun filledIconButton_medium_squareShape() {
+ var shape: Shape = CircleShape
+ val background = Color.Yellow
+ val iconButtonColor = Color.Blue
+ rule.setMaterialContent(lightColorScheme()) {
+ shape = IconButtonDefaults.mediumSquareShape
+ Surface(color = background) {
+ Box {
+ FilledIconButton(
+ onClick = { /* doSomething() */ },
+ modifier =
+ Modifier.semantics(mergeDescendants = true) {}
+ .testTag(IconTestTag)
+ .size(IconButtonDefaults.mediumContainerSize()),
+ shape = shape,
+ colors =
+ IconButtonDefaults.iconButtonColors(containerColor = iconButtonColor)
+ ) {}
+ }
+ }
+ }
+
+ rule
+ .onNodeWithTag(IconTestTag)
+ .captureToImage()
+ .assertShape(
+ density = rule.density,
+ shape = shape,
+ shapeColor = iconButtonColor,
+ backgroundColor = background,
+ shapeOverlapPixelCount = with(rule.density) { 1.dp.toPx() }
+ )
+ }
+
+ @Test
fun filledIconButton_sizeWithoutMinTargetEnforcement() {
rule
.setMaterialContentForSizeAssertions {
@@ -972,7 +1227,7 @@
}
@Test
- fun outlinedIconToggleButton_defaualtColors() {
+ fun outlinedIconToggleButton_defaultColors() {
rule.setMaterialContent(lightColorScheme()) {
val localContentColor = LocalContentColor.current
Truth.assertThat(IconButtonDefaults.outlinedIconToggleButtonColors())
@@ -994,8 +1249,27 @@
}
}
+ private fun assertVisualBounds(composable: @Composable () -> Unit, expectedSize: IntSize) {
+ // The bounds of a testTag on a box that contains the progress indicator are not affected
+ // by the padding added on the layout of the progress bar.
+ rule.setContent { composable() }
+
+ val node =
+ rule
+ .onNodeWithTag(IconButtonTestTag)
+ .fetchSemanticsNode(
+ errorMessageOnFail = "couldn't find node with tag $IconButtonTestTag"
+ )
+ val nodeBounds = node.boundsInRoot
+
+ // Check that the visual bounds of an xsmall icon button are the expected visual size.
+ assertEquals(expectedSize.width.toFloat(), nodeBounds.width)
+ assertEquals(expectedSize.height.toFloat(), nodeBounds.height)
+ }
+
private val IconButtonAccessibilitySize = 48.0.dp
private val IconButtonSize = 40.0.dp
private val IconSize = 24.0.dp
private val IconTestTag = "icon"
+ private val IconButtonTestTag = "iconButton"
}
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/InteractionSourceModifierNodeTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/InteractionSourceModifierNodeTest.kt
new file mode 100644
index 0000000..ca6ced6
--- /dev/null
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/InteractionSourceModifierNodeTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.hasClickAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.util.fastForEach
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import junit.framework.TestCase.assertTrue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InteractionSourceModifierNodeTest {
+
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun interactionSourceData_findInteractionSources() {
+ lateinit var sources: MutableState<List<InteractionSource>>
+ lateinit var scope: CoroutineScope
+ var childElementPressed = false
+
+ rule.setContent {
+ sources = remember { mutableStateOf(emptyList()) }
+ scope = rememberCoroutineScope()
+ val interactionSource = remember { MutableInteractionSource() }
+ Box(
+ modifier =
+ Modifier.onChildrenInteractionSourceChange { interactionSources ->
+ if (sources.value != interactionSources) {
+ sources.value = interactionSources
+ }
+ }
+ ) {
+ Box(
+ modifier =
+ Modifier.clickable(
+ interactionSource = interactionSource,
+ indication = ripple(),
+ onClick = {}
+ )
+ .interactionSourceData(interactionSource)
+ )
+ }
+ }
+
+ rule.waitForIdle()
+ // Set up observer and change childElementPressd to true
+ sources.value.fastForEach { interactionSource ->
+ scope.launch {
+ interactionSource.interactions.collectLatest { interaction ->
+ when (interaction) {
+ is PressInteraction.Press -> {
+ childElementPressed = true
+ }
+ }
+ }
+ }
+ }
+
+ rule.onNode(hasClickAction()).performClick()
+
+ assertTrue(childElementPressed)
+ }
+}
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailTest.kt
index 34e6423..33d5274 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailTest.kt
@@ -18,19 +18,25 @@
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material3.internal.Strings
+import androidx.compose.material3.internal.getString
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
import androidx.compose.ui.test.isDisplayed
import androidx.compose.ui.test.isNotDisplayed
import androidx.compose.ui.test.junit4.StateRestorationTester
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onParent
+import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeLeft
import androidx.compose.ui.unit.dp
@@ -203,6 +209,73 @@
}
@Test
+ fun modalRail_closes_byScrimClick() {
+ lateinit var closeRail: String
+ lateinit var railState: ModalExpandedNavigationRailState
+ rule.setMaterialContentForSizeAssertions {
+ closeRail = getString(Strings.CloseRail)
+ railState = rememberModalExpandedNavigationRailState()
+
+ ModalExpandedNavigationRail(
+ gesturesEnabled = false,
+ onDismissRequest = {},
+ railState = railState,
+ ) {
+ WideNavigationRailItem(
+ modifier = Modifier.testTag("item"),
+ railExpanded = true,
+ icon = { Icon(Icons.Filled.Favorite, null) },
+ label = { Text("ItemText") },
+ selected = true,
+ onClick = {}
+ )
+ }
+ }
+
+ // The rail should be open.
+ assertThat(railState.isOpen).isTrue()
+
+ rule
+ .onNodeWithContentDescription(closeRail)
+ .assertHasClickAction()
+ .performSemanticsAction(SemanticsActions.OnClick)
+ rule.waitForIdle()
+
+ // Assert rail is not open.
+ assertThat(railState.isOpen).isFalse()
+ // Assert rail is not displayed.
+ rule.onNodeWithTag("item").onParent().isNotDisplayed()
+ }
+
+ @Test
+ fun modalRail_hasPaneTitle() {
+ lateinit var paneTitle: String
+
+ rule.setMaterialContentForSizeAssertions {
+ paneTitle = getString(Strings.WideNavigationRailPaneTitle)
+ ModalExpandedNavigationRail(
+ onDismissRequest = {},
+ ) {
+ WideNavigationRailItem(
+ modifier = Modifier.testTag("item"),
+ railExpanded = true,
+ icon = { Icon(Icons.Filled.Favorite, null) },
+ label = { Text("ItemText") },
+ selected = true,
+ onClick = {}
+ )
+ }
+ }
+
+ rule
+ .onNodeWithTag("item")
+ .onParent() // rail.
+ .onParent() // dialog window.
+ .onParent() // parent container that holds dialog and scrim.
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.PaneTitle, paneTitle))
+ }
+
+ @Test
fun modalRailState_savesAndRestores() {
lateinit var railState: ModalExpandedNavigationRailState
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/Strings.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/Strings.android.kt
index aa629c2..0ca2a35 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/Strings.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/Strings.android.kt
@@ -239,7 +239,13 @@
actual inline val CloseDrawer
get() = Strings(R.string.close_drawer)
+ actual inline val CloseRail
+ get() = Strings(MaterialR.string.m3c_wide_navigation_rail_close_rail)
+
actual inline val CloseSheet
get() = Strings(R.string.close_sheet)
+
+ actual inline val WideNavigationRailPaneTitle
+ get() = Strings(MaterialR.string.m3c_wide_navigation_rail_pane_title)
}
}
diff --git a/compose/material3/material3/src/androidMain/res/values/strings.xml b/compose/material3/material3/src/androidMain/res/values/strings.xml
index 371e90f..9b2e983 100644
--- a/compose/material3/material3/src/androidMain/res/values/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values/strings.xml
@@ -113,4 +113,8 @@
<string name="m3c_time_picker_minute_text_field">for minutes</string>
<!-- A label for a textfield that allows the user to input hours. It reads: Edit Box, for hour -->
<string name="m3c_time_picker_hour_text_field">for hour</string>
+ <!-- Spoken content description of an element which will close the modal rail when clicked. -->
+ <string name="m3c_wide_navigation_rail_close_rail">"Close rail"</string>
+ <!-- Accessibility pane title for the modal rail. -->
+ <string name="m3c_wide_navigation_rail_pane_title">"Navigation rail"</string>
</resources>
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
index e15c050..8c63470 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
@@ -1587,7 +1587,6 @@
* @param flingAnimationSpec an optional [DecayAnimationSpec] that defined how to fling the top
* app bar when the user flings the app bar itself, or the content below it
*/
- @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@ExperimentalMaterial3Api
@Composable
fun enterAlwaysScrollBehavior(
@@ -1622,7 +1621,6 @@
* @param flingAnimationSpec an optional [DecayAnimationSpec] that defined how to fling the top
* app bar when the user flings the app bar itself, or the content below it
*/
- @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@ExperimentalMaterial3Api
@Composable
fun exitUntilCollapsedScrollBehavior(
@@ -1994,7 +1992,6 @@
* @param flingAnimationSpec an optional [DecayAnimationSpec] that defined how to fling the
* bottom app bar when the user flings the app bar itself, or the content below it
*/
- @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@ExperimentalMaterial3Api
@Composable
fun exitAlwaysScrollBehavior(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ButtonGroup.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ButtonGroup.kt
new file mode 100644
index 0000000..5de40c3
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ButtonGroup.kt
@@ -0,0 +1,423 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.annotation.FloatRange
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.material3.tokens.ButtonGroupSmallTokens
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.IntrinsicMeasurable
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.ParentDataModifierNode
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.util.fastMapIndexed
+import androidx.compose.ui.util.fastMaxBy
+import androidx.compose.ui.util.fastRoundToInt
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.roundToInt
+import kotlin.math.sign
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/**
+ * TODO link to mio page when available.
+ *
+ * A layout composable that places its children in a horizontal sequence. When a child uses
+ * [Modifier.interactionSourceData] with a relevant [MutableInteractionSource], this button group
+ * can listen to the interactions and expand the width of the pressed child element as well as
+ * compress the neighboring child elements. Material3 components already use
+ * [Modifier.interactionSourceData] and will behave as expected.
+ *
+ * TODO link to an image when available
+ *
+ * @sample androidx.compose.material3.samples.ButtonGroupSample
+ * @param modifier the [Modifier] to be applied to the button group.
+ * @param animateFraction the percentage, represented by a float, of the width of the interacted
+ * child element that will be used to expand the interacted child element as well as compress the
+ * neighboring children.
+ * @param horizontalArrangement The horizontal arrangement of the button group's children.
+ * @param content the content displayed in the button group, expected to use a Material3 component
+ * or a composable that is tagged with [Modifier.interactionSourceData].
+ */
+@Composable
+@ExperimentalMaterial3ExpressiveApi
+fun ButtonGroup(
+ modifier: Modifier = Modifier,
+ @FloatRange(0.0) animateFraction: Float = ButtonGroupDefaults.animateFraction,
+ horizontalArrangement: Arrangement.Horizontal =
+ Arrangement.spacedBy(ButtonGroupDefaults.spaceBetween),
+ content: @Composable ButtonGroupScope.() -> Unit
+) {
+ val anim = remember { Animatable(0f) }
+ val coroutineScope = rememberCoroutineScope()
+ var pressedIndex by remember { mutableIntStateOf(-1) }
+ val scope = remember {
+ object : ButtonGroupScope {
+ override fun Modifier.weight(weight: Float, fill: Boolean): Modifier {
+ require(weight > 0.0) { "invalid weight $weight; must be greater than zero" }
+ return this.then(
+ LayoutWeightElement(
+ // Coerce Float.POSITIVE_INFINITY to Float.MAX_VALUE to avoid errors
+ weight = weight.coerceAtMost(Float.MAX_VALUE),
+ fill = fill
+ )
+ )
+ }
+ }
+ }
+
+ val interactionSourceFlow: MutableStateFlow<List<InteractionSource>> = remember {
+ MutableStateFlow(emptyList())
+ }
+
+ LaunchedEffect(Unit) {
+ interactionSourceFlow.collectLatest { sources ->
+ sources.fastForEachIndexed { index, interactionSource ->
+ launch {
+ interactionSource.interactions.collectLatest { interaction ->
+ when (interaction) {
+ is PressInteraction.Press -> {
+ pressedIndex = index
+ coroutineScope.launch { anim.animateTo(animateFraction) }
+ }
+ is PressInteraction.Release,
+ is PressInteraction.Cancel -> {
+ coroutineScope.launch {
+ anim.animateTo(0f)
+ pressedIndex = -1
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ val measurePolicy =
+ remember(horizontalArrangement) {
+ ButtonGroupMeasurePolicy(
+ horizontalArrangement = horizontalArrangement,
+ anim = anim,
+ pressedIndex = { pressedIndex }
+ )
+ }
+
+ Layout(
+ measurePolicy = measurePolicy,
+ modifier =
+ modifier.onChildrenInteractionSourceChange { interactionSource ->
+ if (interactionSourceFlow.value != interactionSource) {
+ coroutineScope.launch { interactionSourceFlow.emit(interactionSource) }
+ }
+ },
+ content = { scope.content() }
+ )
+}
+
+/** Default values used by [ButtonGroup] */
+object ButtonGroupDefaults {
+ /**
+ * The default percentage, represented as a float, of the width of the interacted child element
+ * that will be used to expand the interacted child element as well as compress the neighboring
+ * children.
+ */
+ val animateFraction = 0.15f
+
+ /** The default spacing used between children. */
+ val spaceBetween = ButtonGroupSmallTokens.BetweenSpace
+}
+
+private class ButtonGroupMeasurePolicy(
+ val horizontalArrangement: Arrangement.Horizontal,
+ val anim: Animatable<Float, AnimationVector1D>,
+ val pressedIndex: () -> Int,
+) : MeasurePolicy {
+ override fun MeasureScope.measure(
+ measurables: List<Measurable>,
+ constraints: Constraints
+ ): MeasureResult {
+ val arrangementSpacingInt = horizontalArrangement.spacing.roundToPx()
+ val arrangementSpacingPx = arrangementSpacingInt.toLong()
+ val size = measurables.size
+ var totalWeight = 0f
+ var fixedSpace = 0
+ var weightChildrenCount = 0
+ val placeables: List<Placeable>
+ val childrenMainAxisSize = IntArray(size)
+ val childrenConstraints: Array<Constraints?> = arrayOfNulls(size)
+
+ val mainAxisMin = constraints.minWidth
+ val mainAxisMax = constraints.maxWidth
+
+ // First obtain constraints of children with zero weight
+ var spaceAfterLastNoWeight = 0
+ for (i in 0 until size) {
+ val child = measurables[i]
+ val parentData = child.buttonGroupParentData
+ val weight = parentData.weight
+
+ if (weight > 0f) {
+ totalWeight += weight
+ ++weightChildrenCount
+ } else {
+ val remaining = mainAxisMax - fixedSpace
+ val desiredWidth = child.maxIntrinsicWidth(constraints.maxHeight)
+ childrenConstraints[i] =
+ constraints.copy(
+ minWidth = 0,
+ maxWidth =
+ if (mainAxisMax == Constraints.Infinity) {
+ Constraints.Infinity
+ } else {
+ desiredWidth.coerceAtLeast(0)
+ }
+ )
+
+ childrenMainAxisSize[i] = desiredWidth
+
+ spaceAfterLastNoWeight =
+ min(arrangementSpacingInt, (remaining - desiredWidth).coerceAtLeast(0))
+
+ fixedSpace += desiredWidth + spaceAfterLastNoWeight
+ }
+ }
+
+ var weightedSpace = 0
+ if (weightChildrenCount == 0) {
+ // fixedSpace contains an extra spacing after the last non-weight child.
+ fixedSpace -= spaceAfterLastNoWeight
+ } else {
+ // obtain the constraints of the rest according to their weights.
+ val targetSpace =
+ if (mainAxisMax != Constraints.Infinity) {
+ mainAxisMax
+ } else {
+ mainAxisMin
+ }
+
+ val arrangementSpacingTotal = arrangementSpacingPx * (weightChildrenCount - 1)
+ val remainingToTarget =
+ (targetSpace - fixedSpace - arrangementSpacingTotal).coerceAtLeast(0)
+
+ val weightUnitSpace = remainingToTarget / totalWeight
+ var remainder = remainingToTarget
+ for (i in 0 until size) {
+ val measurable = measurables[i]
+ val itemWeight = measurable.buttonGroupParentData.weight
+ val weightedSize = (weightUnitSpace * itemWeight)
+ remainder -= weightedSize.fastRoundToInt()
+ }
+
+ for (i in 0 until size) {
+ if (childrenConstraints[i] == null) {
+ val child = measurables[i]
+ val parentData = child.buttonGroupParentData
+ val weight = parentData.weight
+
+ // After the weightUnitSpace rounding, the total space going to be occupied
+ // can be smaller or larger than remainingToTarget. Here we distribute the
+ // loss or gain remainder evenly to the first children.
+ val remainderUnit = remainder.sign
+ remainder -= remainderUnit
+ val weightedSize = (weightUnitSpace * weight)
+
+ val childMainAxisSize = max(0, weightedSize.fastRoundToInt() + remainderUnit)
+
+ childrenConstraints[i] =
+ constraints.copy(
+ minWidth =
+ if (parentData.fill && childMainAxisSize != Constraints.Infinity) {
+ childMainAxisSize
+ } else {
+ 0
+ },
+ maxWidth = childMainAxisSize
+ )
+
+ childrenMainAxisSize[i] = childMainAxisSize
+ weightedSpace += childMainAxisSize
+ }
+ weightedSpace =
+ (weightedSpace + arrangementSpacingTotal)
+ .toInt()
+ .coerceIn(0, mainAxisMax - fixedSpace)
+ }
+ }
+
+ val pressedIdx = pressedIndex.invoke()
+ if (pressedIdx == -1 || pressedIdx >= measurables.size) {
+ placeables =
+ measurables.fastMapIndexed { index, measurable ->
+ measurable.measure(childrenConstraints[index] ?: constraints)
+ }
+ } else {
+ val adjacent = buildList {
+ measurables.getOrNull(pressedIdx - 1)?.let { add(it) }
+ measurables.getOrNull(pressedIdx + 1)?.let { add(it) }
+ }
+
+ val pressedMeasurable = measurables[pressedIdx]
+ val pressedWidth = (childrenConstraints[pressedIdx] ?: constraints).maxWidth
+ val additionFactor = anim.value
+ val subtractFactor =
+ if (pressedIdx == 0 || pressedIdx == size - 1) anim.value else anim.value / 2f
+
+ placeables =
+ measurables.fastMapIndexed { index, measurable ->
+ val desiredWidth = (childrenConstraints[index] ?: constraints).maxWidth
+ if (measurable == pressedMeasurable) {
+ measurable.measure(
+ Constraints.fixedWidth(
+ (desiredWidth + (pressedWidth * additionFactor)).roundToInt()
+ )
+ )
+ } else if (measurable in adjacent) {
+ measurable.measure(
+ Constraints.fixedWidth(
+ (desiredWidth - (pressedWidth * subtractFactor)).roundToInt()
+ )
+ )
+ } else {
+ measurable.measure(childrenConstraints[index] ?: constraints)
+ }
+ }
+ }
+
+ // Compute the row size and position the children.
+ val mainAxisLayoutSize = max((fixedSpace + weightedSpace).coerceAtLeast(0), mainAxisMin)
+ val mainAxisPositions = IntArray(size) { 0 }
+ val measureScope = this
+ with(horizontalArrangement) {
+ measureScope.arrange(
+ mainAxisLayoutSize,
+ childrenMainAxisSize,
+ measureScope.layoutDirection,
+ mainAxisPositions
+ )
+ }
+
+ val height = placeables.fastMaxBy { it.height }?.height ?: constraints.minHeight
+ return layout(mainAxisLayoutSize, height) {
+ var currentX = 0
+ placeables.fastForEach {
+ it.place(currentX, 0)
+ currentX += it.width + arrangementSpacingInt
+ }
+ }
+ }
+}
+
+/** Button group scope used to indicate a [Modifier.weight] of a child element. */
+@ExperimentalMaterial3ExpressiveApi
+interface ButtonGroupScope {
+ /**
+ * Size the element's width proportional to its [weight] relative to other weighted sibling
+ * elements in the [ButtonGroup]. The parent will divide the horizontal space remaining after
+ * measuring unweighted child elements and distribute it according to this weight. When [fill]
+ * is true, the element will be forced to occupy the whole width allocated to it. Otherwise, the
+ * element is allowed to be smaller - this will result in [ButtonGroup] being smaller, as the
+ * unused allocated width will not be redistributed to other siblings.
+ *
+ * @param weight The proportional width to give to this element, as related to the total of all
+ * weighted siblings. Must be positive.
+ * @param fill When `true`, the element will occupy the whole width allocated.
+ */
+ fun Modifier.weight(
+ @FloatRange(from = 0.0, fromInclusive = false) weight: Float,
+ fill: Boolean = true
+ ): Modifier
+}
+
+internal val IntrinsicMeasurable.buttonGroupParentData: ButtonGroupParentData?
+ get() = parentData as? ButtonGroupParentData
+
+internal val ButtonGroupParentData?.fill: Boolean
+ get() = this?.fill ?: true
+
+internal val ButtonGroupParentData?.weight: Float
+ get() = this?.weight ?: 0f
+
+internal data class ButtonGroupParentData(var weight: Float = 0f, var fill: Boolean = true)
+
+internal class LayoutWeightElement(
+ val weight: Float,
+ val fill: Boolean,
+) : ModifierNodeElement<LayoutWeightNode>() {
+ override fun create(): LayoutWeightNode {
+ return LayoutWeightNode(weight, fill)
+ }
+
+ override fun update(node: LayoutWeightNode) {
+ node.weight = weight
+ node.fill = fill
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "weight"
+ value = weight
+ properties["weight"] = weight
+ properties["fill"] = fill
+ }
+
+ override fun hashCode(): Int {
+ var result = weight.hashCode()
+ result = 31 * result + fill.hashCode()
+ return result
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ val otherModifier = other as? LayoutWeightElement ?: return false
+ return weight == otherModifier.weight && fill == otherModifier.fill
+ }
+}
+
+internal class LayoutWeightNode(
+ var weight: Float,
+ var fill: Boolean,
+) : ParentDataModifierNode, Modifier.Node() {
+ override fun Density.modifyParentData(parentData: Any?) =
+ ((parentData as? ButtonGroupParentData) ?: ButtonGroupParentData()).also {
+ it.weight = weight
+ it.fill = fill
+ }
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
index 84c8d9c..0fd3f81 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
@@ -257,7 +257,6 @@
}
}
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun CheckboxImpl(
enabled: Boolean,
@@ -266,15 +265,15 @@
colors: CheckboxColors
) {
val transition = updateTransition(value)
+ val defaultAnimationSpec = MotionSchemeKeyTokens.DefaultSpatial.value<Float>()
val checkDrawFraction =
transition.animateFloat(
transitionSpec = {
when {
// TODO Load the motionScheme tokens from the component tokens file
- initialState == ToggleableState.Off ->
- MotionSchemeKeyTokens.DefaultSpatial.value()
+ initialState == ToggleableState.Off -> defaultAnimationSpec
targetState == ToggleableState.Off -> snap(delayMillis = SnapAnimationDelay)
- else -> MotionSchemeKeyTokens.DefaultSpatial.value()
+ else -> defaultAnimationSpec
}
}
) {
@@ -292,7 +291,7 @@
// TODO Load the motionScheme tokens from the component tokens file
initialState == ToggleableState.Off -> snap()
targetState == ToggleableState.Off -> snap(delayMillis = SnapAnimationDelay)
- else -> MotionSchemeKeyTokens.DefaultSpatial.value()
+ else -> defaultAnimationSpec
}
}
) {
@@ -554,7 +553,6 @@
}
/** Returns the color [AnimationSpec] for the given state. */
- @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun colorAnimationSpecForState(state: ToggleableState): AnimationSpec<Color> {
// TODO Load the motionScheme tokens from the component tokens file
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
index fc3c261..3857026 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
@@ -690,7 +690,6 @@
* @param lazyListState a [LazyListState]
* @param decayAnimationSpec the decay to use
*/
- @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
internal fun rememberSnapFlingBehavior(
lazyListState: LazyListState,
@@ -787,7 +786,6 @@
* @constructor create an instance with arbitrary colors, see [DatePickerDefaults.colors] for the
* default implementation that follows Material specifications.
*/
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@ExperimentalMaterial3Api
@Immutable
class DatePickerColors
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
index d0d23f8..0fc77f6 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
@@ -903,18 +903,12 @@
) {
val expandTransition = updateTransition(if (expanded) 1f else 0f, label = "expanded state")
// TODO Load the motionScheme tokens from the component tokens file
+ val sizeAnimationSpec = MotionSchemeKeyTokens.FastSpatial.value<Float>()
+ val opacityAnimationSpec = MotionSchemeKeyTokens.FastEffects.value<Float>()
val expandedWidthProgress =
- expandTransition.animateFloat(
- transitionSpec = { MotionSchemeKeyTokens.FastSpatial.value() }
- ) {
- it
- }
+ expandTransition.animateFloat(transitionSpec = { sizeAnimationSpec }) { it }
val expandedAlphaProgress =
- expandTransition.animateFloat(
- transitionSpec = { MotionSchemeKeyTokens.FastEffects.value() }
- ) {
- it
- }
+ expandTransition.animateFloat(transitionSpec = { opacityAnimationSpec }) { it }
Row(
modifier =
Modifier.layout { measurable, constraints ->
@@ -1264,7 +1258,6 @@
private val ExtendedFabMinimumWidth = 80.dp
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun extendedFabCollapseAnimation() =
fadeOut(
@@ -1276,7 +1269,6 @@
shrinkTowards = Alignment.Start,
)
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun extendedFabExpandAnimation() =
fadeIn(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingAppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingAppBar.kt
index 1f15609..439bf25 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingAppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingAppBar.kt
@@ -398,6 +398,7 @@
* @param flingAnimationSpec an [DecayAnimationSpec] that defines how to fling the floating app
* bar when the user flings the app bar itself, or the content below it
*/
+ // TODO Load the motionScheme tokens from the component tokens file
@ExperimentalMaterial3ExpressiveApi
@Composable
fun exitAlwaysScrollBehavior(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
index 3dae87d..35c1914 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
@@ -26,9 +26,13 @@
import androidx.compose.material3.internal.childSemantics
import androidx.compose.material3.tokens.FilledIconButtonTokens
import androidx.compose.material3.tokens.FilledTonalIconButtonTokens
-import androidx.compose.material3.tokens.IconButtonSmallTokens
import androidx.compose.material3.tokens.IconButtonTokens
+import androidx.compose.material3.tokens.LargeIconButtonTokens
+import androidx.compose.material3.tokens.MediumIconButtonTokens
import androidx.compose.material3.tokens.OutlinedIconButtonTokens
+import androidx.compose.material3.tokens.SmallIconButtonTokens
+import androidx.compose.material3.tokens.XLargeIconButtonTokens
+import androidx.compose.material3.tokens.XSmallIconButtonTokens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
@@ -47,6 +51,8 @@
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import kotlin.jvm.JvmInline
/**
* <a href="https://m3.material.io/components/icon-button/overview" class="external"
@@ -64,8 +70,19 @@
*
* Simple Usage
*
- * @sample androidx.compose.material3.samples.IconButtonSample IconButton with a color tint
+ * @sample androidx.compose.material3.samples.IconButtonSample
+ *
+ * IconButton with a color tint
+ *
* @sample androidx.compose.material3.samples.TintedIconButtonSample
+ *
+ * Small-sized narrow round shape IconButton
+ *
+ * @sample androidx.compose.material3.samples.XSmallNarrowSquareIconButtonsSample
+ *
+ * Medium / default size round-shaped icon button
+ *
+ * @sample androidx.compose.material3.samples.MediumRoundWideIconButtonSample
* @param onClick called when this icon button is clicked
* @param modifier the [Modifier] to be applied to this icon button
* @param enabled controls the enabled state of this icon button. When `false`, this component will
@@ -128,14 +145,6 @@
* IconButton with a color tint
*
* @sample androidx.compose.material3.samples.TintedIconButtonSample
- *
- * IconButton with smaller square narrow shape
- *
- * @sample androidx.compose.material3.samples.SmallSquareNarrowIconButtonSample
- *
- * IconButton with smaller square narrow shape
- *
- * @sample androidx.compose.material3.samples.SmallRoundWideIconButtonSample
* @param onClick called when this icon button is clicked
* @param modifier the [Modifier] to be applied to this icon button
* @param enabled controls the enabled state of this icon button. When `false`, this component will
@@ -160,6 +169,8 @@
shape: Shape = IconButtonDefaults.standardShape,
content: @Composable () -> Unit
) {
+ @Suppress("NAME_SHADOWING")
+ val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
Box(
modifier =
modifier
@@ -174,7 +185,8 @@
interactionSource = interactionSource,
indication = ripple()
)
- .childSemantics(),
+ .childSemantics()
+ .interactionSourceData(interactionSource),
contentAlignment = Alignment.Center
) {
val contentColor = colors.contentColor(enabled)
@@ -283,6 +295,8 @@
shape: Shape = IconButtonDefaults.standardShape,
content: @Composable () -> Unit
) {
+ @Suppress("NAME_SHADOWING")
+ val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
Box(
modifier =
modifier
@@ -297,7 +311,8 @@
role = Role.Checkbox,
interactionSource = interactionSource,
indication = ripple()
- ),
+ )
+ .interactionSourceData(interactionSource),
contentAlignment = Alignment.Center
) {
val contentColor = colors.contentColor(enabled, checked).value
@@ -346,26 +361,16 @@
interactionSource: MutableInteractionSource? = null,
content: @Composable () -> Unit
) =
- Surface(
+ SurfaceIconButton(
onClick = onClick,
- modifier = modifier.semantics { role = Role.Button },
+ modifier = modifier,
enabled = enabled,
shape = shape,
- color = colors.containerColor(enabled),
- contentColor = colors.contentColor(enabled),
- interactionSource = interactionSource
- ) {
- Box(
- modifier =
- Modifier.size(
- width = FilledIconButtonTokens.ContainerWidth,
- height = FilledIconButtonTokens.ContainerHeight
- ),
- contentAlignment = Alignment.Center
- ) {
- content()
- }
- }
+ colors = colors,
+ border = null,
+ interactionSource = interactionSource,
+ content = content
+ )
/**
* <a href="https://m3.material.io/components/icon-button/overview" class="external"
@@ -412,26 +417,16 @@
interactionSource: MutableInteractionSource? = null,
content: @Composable () -> Unit
) =
- Surface(
+ SurfaceIconButton(
onClick = onClick,
- modifier = modifier.semantics { role = Role.Button },
+ modifier = modifier,
enabled = enabled,
shape = shape,
- color = colors.containerColor(enabled),
- contentColor = colors.contentColor(enabled),
- interactionSource = interactionSource
- ) {
- Box(
- modifier =
- Modifier.size(
- width = FilledTonalIconButtonTokens.ContainerWidth,
- height = FilledTonalIconButtonTokens.ContainerHeight
- ),
- contentAlignment = Alignment.Center
- ) {
- content()
- }
- }
+ colors = colors,
+ border = null,
+ interactionSource = interactionSource,
+ content = content
+ )
/**
* <a href="https://m3.material.io/components/icon-button/overview" class="external"
@@ -589,6 +584,10 @@
* button has an overall minimum touch target size of 48 x 48dp, to meet accessibility guidelines.
*
* @sample androidx.compose.material3.samples.OutlinedIconButtonSample
+ *
+ * Large-sized uniform rounded shape
+ *
+ * @sample androidx.compose.material3.samples.LargeRoundUniformOutlinedIconButtonSample
* @param onClick called when this icon button is clicked
* @param modifier the [Modifier] to be applied to this icon button
* @param enabled controls the enabled state of this icon button. When `false`, this component will
@@ -617,6 +616,28 @@
interactionSource: MutableInteractionSource? = null,
content: @Composable () -> Unit
) =
+ SurfaceIconButton(
+ onClick = onClick,
+ modifier = modifier,
+ enabled = enabled,
+ shape = shape,
+ colors = colors,
+ border = border,
+ interactionSource = interactionSource,
+ content = content
+ )
+
+@Composable
+private fun SurfaceIconButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ enabled: Boolean,
+ shape: Shape,
+ colors: IconButtonColors,
+ border: BorderStroke?,
+ interactionSource: MutableInteractionSource?,
+ content: @Composable () -> Unit
+) =
Surface(
onClick = onClick,
modifier = modifier.semantics { role = Role.Button },
@@ -628,7 +649,11 @@
interactionSource = interactionSource
) {
Box(
- modifier = Modifier.size(OutlinedIconButtonTokens.ContainerSize),
+ modifier =
+ Modifier.size(
+ width = FilledTonalIconButtonTokens.ContainerWidth,
+ height = FilledTonalIconButtonTokens.ContainerHeight
+ ),
contentAlignment = Alignment.Center
) {
content()
@@ -704,69 +729,6 @@
/** Contains the default values used by all icon button types. */
object IconButtonDefaults {
- /** Default ripple shape for a standard icon button. */
- val standardShape: Shape
- @Composable get() = IconButtonTokens.StateLayerShape.value
-
- /** Default shape for a filled icon button. */
- val filledShape: Shape
- @Composable get() = FilledIconButtonTokens.ContainerShape.value
-
- /** Default shape for an outlined icon button. */
- val outlinedShape: Shape
- @Composable get() = OutlinedIconButtonTokens.ContainerShape.value
-
- @ExperimentalMaterial3ExpressiveApi
- /** Default round shape for any icon button. */
- val roundShape: Shape
- @Composable get() = IconButtonSmallTokens.ContainerShapeRound.value
-
- @ExperimentalMaterial3ExpressiveApi
- /** Default square shape for any icon button. */
- val squareShape: Shape
- @Composable get() = IconButtonSmallTokens.ContainerShapeSquare.value
-
- @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
- @get:ExperimentalMaterial3ExpressiveApi
- @ExperimentalMaterial3ExpressiveApi
- /** Default small narrow container for any icon button. */
- val SmallIconSize: Dp = IconButtonSmallTokens.IconSize
-
- @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
- @get:ExperimentalMaterial3ExpressiveApi
- @ExperimentalMaterial3ExpressiveApi
- /** Default small narrow container for any icon button. */
- val SmallNarrowContainerSize: DpSize =
- DpSize(
- IconButtonSmallTokens.IconSize +
- IconButtonSmallTokens.NarrowLeadingSpace +
- IconButtonSmallTokens.NarrowTrailingSpace,
- IconButtonSmallTokens.ContainerHeight
- )
-
- @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
- @get:ExperimentalMaterial3ExpressiveApi
- @ExperimentalMaterial3ExpressiveApi
- /** Default small narrow container for any icon button. */
- val SmallContainerSize: DpSize =
- DpSize(
- IconButtonSmallTokens.IconSize +
- IconButtonSmallTokens.UniformLeadingSpace +
- IconButtonSmallTokens.UniformLeadingSpace,
- IconButtonSmallTokens.ContainerHeight
- )
-
- @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
- @get:ExperimentalMaterial3ExpressiveApi
- @ExperimentalMaterial3ExpressiveApi
- /** Default small narrow container for any icon button. */
- val SmallWideContainerSize: DpSize =
- DpSize(
- IconButtonSmallTokens.IconSize +
- IconButtonSmallTokens.WideLeadingSpace +
- IconButtonSmallTokens.WideTrailingSpace,
- IconButtonSmallTokens.ContainerHeight
- )
/** Creates a [IconButtonColors] that represents the default colors used in a [IconButton]. */
@Composable
@@ -1282,6 +1244,286 @@
BorderStroke(OutlinedIconButtonTokens.UnselectedOutlineWidth, color)
}
}
+
+ /** Default ripple shape for a standard icon button. */
+ val standardShape: Shape
+ @Composable get() = IconButtonTokens.StateLayerShape.value
+
+ /** Default shape for a filled icon button. */
+ val filledShape: Shape
+ @Composable get() = FilledIconButtonTokens.ContainerShape.value
+
+ /** Default shape for an outlined icon button. */
+ val outlinedShape: Shape
+ @Composable get() = OutlinedIconButtonTokens.ContainerShape.value
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Default round shape for any extra small icon button. */
+ val xSmallRoundShape: Shape
+ @Composable get() = XSmallIconButtonTokens.ContainerShapeRound.value
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Default square shape for any extra small icon button. */
+ val xSmallSquareShape: Shape
+ @Composable get() = XSmallIconButtonTokens.ContainerShapeSquare.value
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Default shape for any small icon button. */
+ val smallRoundShape: Shape
+ @Composable get() = SmallIconButtonTokens.ContainerShapeRound.value
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Default shape for any small icon button. */
+ val smallSquareShape: Shape
+ @Composable get() = SmallIconButtonTokens.ContainerShapeSquare.value
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Default shape for any medium icon button. */
+ val mediumRoundShape: Shape
+ @Composable get() = MediumIconButtonTokens.ContainerShapeRound.value
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Default shape for any medium icon button. */
+ val mediumSquareShape: Shape
+ @Composable get() = MediumIconButtonTokens.ContainerShapeSquare.value
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Default shape for any large icon button. */
+ val largeRoundShape: Shape
+ @Composable get() = LargeIconButtonTokens.ContainerShapeRound.value
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Default shape for any large icon button. */
+ val largeSquareShape: Shape
+ @Composable get() = LargeIconButtonTokens.ContainerShapeSquare.value
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Default shape for any xlarge icon button. */
+ val xLargeRoundShape: Shape
+ @Composable get() = XLargeIconButtonTokens.ContainerShapeRound.value
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Default shape for any xlarge icon button. */
+ val xLargeSquareShape: Shape
+ @Composable get() = XLargeIconButtonTokens.ContainerShapeSquare.value
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Default container for any extra small icon button. */
+ val xSmallIconSize: Dp = XSmallIconButtonTokens.IconSize
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Default size for any small icon button. */
+ val smallIconSize: Dp = SmallIconButtonTokens.IconSize
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Default container size for any medium icon button. */
+ val mediumIconSize: Dp = MediumIconButtonTokens.IconSize
+
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ /** Default size for any large icon button. */
+ val largeIconSize: Dp = LargeIconButtonTokens.IconSize
+
+ /** Default size for any xlarge icon button. */
+ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+ @get:ExperimentalMaterial3ExpressiveApi
+ @ExperimentalMaterial3ExpressiveApi
+ val xLargeIconSize: Dp = XLargeIconButtonTokens.IconSize
+
+ /**
+ * Default container size for any extra small icon button.
+ *
+ * @param widthOption the width of the container
+ */
+ @ExperimentalMaterial3ExpressiveApi
+ fun xSmallContainerSize(
+ widthOption: IconButtonWidthOption = IconButtonWidthOption.Uniform
+ ): DpSize {
+ val horizontalSpace =
+ when (widthOption) {
+ IconButtonWidthOption.Narrow ->
+ XSmallIconButtonTokens.NarrowLeadingSpace +
+ XSmallIconButtonTokens.NarrowTrailingSpace
+ IconButtonWidthOption.Uniform ->
+ XSmallIconButtonTokens.UniformLeadingSpace +
+ XSmallIconButtonTokens.UniformLeadingSpace
+ IconButtonWidthOption.Wide ->
+ XSmallIconButtonTokens.WideLeadingSpace +
+ XSmallIconButtonTokens.WideTrailingSpace
+ else -> 0.dp
+ }
+ return DpSize(
+ XSmallIconButtonTokens.IconSize + horizontalSpace,
+ XSmallIconButtonTokens.ContainerHeight
+ )
+ }
+
+ /**
+ * Default container size for any small icon button.
+ *
+ * @param widthOption the width of the container
+ */
+ @ExperimentalMaterial3ExpressiveApi
+ fun smallContainerSize(
+ widthOption: IconButtonWidthOption = IconButtonWidthOption.Uniform
+ ): DpSize {
+ val horizontalSpace =
+ when (widthOption) {
+ IconButtonWidthOption.Narrow ->
+ SmallIconButtonTokens.NarrowLeadingSpace +
+ SmallIconButtonTokens.NarrowTrailingSpace
+ IconButtonWidthOption.Uniform ->
+ SmallIconButtonTokens.UniformLeadingSpace +
+ SmallIconButtonTokens.UniformLeadingSpace
+ IconButtonWidthOption.Wide ->
+ SmallIconButtonTokens.WideLeadingSpace + SmallIconButtonTokens.WideTrailingSpace
+ else -> 0.dp
+ }
+ return DpSize(
+ SmallIconButtonTokens.IconSize + horizontalSpace,
+ SmallIconButtonTokens.ContainerHeight
+ )
+ }
+
+ /**
+ * Default container size for any medium icon button.
+ *
+ * @param widthOption the width of the container
+ */
+ @ExperimentalMaterial3ExpressiveApi
+ fun mediumContainerSize(
+ widthOption: IconButtonWidthOption = IconButtonWidthOption.Uniform
+ ): DpSize {
+ val horizontalSpace =
+ when (widthOption) {
+ IconButtonWidthOption.Narrow ->
+ MediumIconButtonTokens.NarrowLeadingSpace +
+ MediumIconButtonTokens.NarrowTrailingSpace
+ IconButtonWidthOption.Uniform ->
+ MediumIconButtonTokens.UniformLeadingSpace +
+ MediumIconButtonTokens.UniformLeadingSpace
+ IconButtonWidthOption.Wide ->
+ MediumIconButtonTokens.WideLeadingSpace +
+ MediumIconButtonTokens.WideTrailingSpace
+ else -> 0.dp
+ }
+ return DpSize(
+ MediumIconButtonTokens.IconSize + horizontalSpace,
+ MediumIconButtonTokens.ContainerHeight
+ )
+ }
+
+ /**
+ * Default container size for any large icon button.
+ *
+ * @param widthOption the width of the container
+ */
+ @ExperimentalMaterial3ExpressiveApi
+ fun largeContainerSize(
+ widthOption: IconButtonWidthOption = IconButtonWidthOption.Uniform
+ ): DpSize {
+ val horizontalSpace =
+ when (widthOption) {
+ IconButtonWidthOption.Narrow ->
+ LargeIconButtonTokens.NarrowLeadingSpace +
+ LargeIconButtonTokens.NarrowTrailingSpace
+ IconButtonWidthOption.Uniform ->
+ LargeIconButtonTokens.UniformLeadingSpace +
+ LargeIconButtonTokens.UniformLeadingSpace
+ IconButtonWidthOption.Wide ->
+ LargeIconButtonTokens.WideLeadingSpace + LargeIconButtonTokens.WideTrailingSpace
+ else -> 0.dp
+ }
+ return DpSize(
+ LargeIconButtonTokens.IconSize + horizontalSpace,
+ LargeIconButtonTokens.ContainerHeight
+ )
+ }
+
+ /**
+ * Default container size for any extra large icon button.
+ *
+ * @param widthOption the width of the container
+ */
+ @ExperimentalMaterial3ExpressiveApi
+ fun xLargeContainerSize(
+ widthOption: IconButtonWidthOption = IconButtonWidthOption.Uniform
+ ): DpSize {
+ val horizontalSpace =
+ when (widthOption) {
+ IconButtonWidthOption.Narrow ->
+ XLargeIconButtonTokens.NarrowLeadingSpace +
+ XLargeIconButtonTokens.NarrowTrailingSpace
+ IconButtonWidthOption.Uniform ->
+ XLargeIconButtonTokens.UniformLeadingSpace +
+ XLargeIconButtonTokens.UniformLeadingSpace
+ IconButtonWidthOption.Wide ->
+ XLargeIconButtonTokens.WideLeadingSpace +
+ XLargeIconButtonTokens.WideTrailingSpace
+ else -> 0.dp
+ }
+ return DpSize(
+ XLargeIconButtonTokens.IconSize + horizontalSpace,
+ XLargeIconButtonTokens.ContainerHeight
+ )
+ }
+
+ /** Class that describes the different supported widths of the [IconButton]. */
+ @JvmInline
+ value class IconButtonWidthOption private constructor(private val value: Int) {
+ companion object {
+ // TODO(b/342666275): update this kdoc with spec guidance
+ /*
+ * This configuration is recommended for small screens.
+ */
+ val Narrow = IconButtonWidthOption(0)
+
+ /*
+ * This configuration is recommended for medium width screens.
+ */
+ val Uniform = IconButtonWidthOption(1)
+
+ /*
+ * This configuration is recommended for wide screens.
+ */
+ val Wide = IconButtonWidthOption(2)
+ }
+
+ override fun toString() =
+ when (this) {
+ Narrow -> "Narrow"
+ Uniform -> "Uniform"
+ Wide -> "Wide"
+ else -> "Unknown"
+ }
+ }
}
/**
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/InteractionSourceModifierNode.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/InteractionSourceModifierNode.kt
new file mode 100644
index 0000000..14a0f0c
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/InteractionSourceModifierNode.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.LayoutAwareModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.TraversableNode
+import androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction
+import androidx.compose.ui.node.traverseDescendants
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.unit.IntSize
+
+/**
+ * Traversable node that holds the interaction source of the current node and provides the ability
+ * to obtain the interaction sources of its descendants.
+ *
+ * @property interactionSource the [MutableInteractionSource] associated with this current node.
+ */
+private class InteractionSourceModifierNode(var interactionSource: MutableInteractionSource) :
+ Modifier.Node(), TraversableNode {
+ override val traverseKey: Any = InteractionSourceModifierNodeTraverseKey
+}
+
+/**
+ * Node that calls [onChildrenInteractionSourceChange] when there is a remeasure due to child
+ * elements changing.
+ *
+ * @property onChildrenInteractionSourceChange callback that is invoked on remeasure.
+ */
+private class OnChildrenInteractionSourceChangeModifierNode(
+ var onChildrenInteractionSourceChange: (List<InteractionSource>) -> Unit
+) : LayoutAwareModifierNode, Modifier.Node(), DelegatableNode {
+ override fun onRemeasured(size: IntSize) {
+ super.onRemeasured(size)
+ onChildrenInteractionSourceChange(findInteractionSources())
+ }
+}
+
+/**
+ * Finds the interaction sources of the descendants of this node that have the same traverse key.
+ */
+internal fun DelegatableNode.findInteractionSources(): List<MutableInteractionSource> {
+ val interactionSources = mutableListOf<MutableInteractionSource>()
+ traverseDescendants(InteractionSourceModifierNodeTraverseKey) {
+ if (it is InteractionSourceModifierNode) {
+ interactionSources.add(it.interactionSource)
+ }
+ TraverseDescendantsAction.SkipSubtreeAndContinueTraversal
+ }
+ return interactionSources
+}
+
+/**
+ * Modifier used to expose an interaction source to a parent.
+ *
+ * @param interactionSource the [MutableInteractionSource] associated with this current node.
+ */
+internal fun Modifier.interactionSourceData(
+ interactionSource: MutableInteractionSource? = null
+): Modifier =
+ this then InteractionSourceModifierElement(interactionSource ?: MutableInteractionSource())
+
+/**
+ * Modifier used to observe interaction sources that are exposed by child elements, child elements
+ * can provide interaction sources using [Modifier.interactionSourceData].
+ *
+ * @param onChildrenInteractionSourceChange callback invoked when children update their interaction
+ * sources.
+ */
+internal fun Modifier.onChildrenInteractionSourceChange(
+ onChildrenInteractionSourceChange: (List<InteractionSource>) -> Unit
+): Modifier =
+ this then OnChildrenInteractionSourceChangeModifierElement(onChildrenInteractionSourceChange)
+
+private data class InteractionSourceModifierElement(
+ private val interactionSource: MutableInteractionSource
+) : ModifierNodeElement<InteractionSourceModifierNode>() {
+ override fun create() = InteractionSourceModifierNode(interactionSource)
+
+ override fun update(node: InteractionSourceModifierNode) {
+ node.interactionSource = interactionSource
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "interactionSourceModifierNode"
+ properties["interactionSource"] = interactionSource
+ }
+}
+
+private data class OnChildrenInteractionSourceChangeModifierElement(
+ private val onChildrenInteractionSourceChange: (List<InteractionSource>) -> Unit
+) : ModifierNodeElement<OnChildrenInteractionSourceChangeModifierNode>() {
+ override fun create() =
+ OnChildrenInteractionSourceChangeModifierNode(onChildrenInteractionSourceChange)
+
+ override fun update(node: OnChildrenInteractionSourceChangeModifierNode) {
+ node.onChildrenInteractionSourceChange = onChildrenInteractionSourceChange
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "onChildrenInteractionSourceChangeModifierNode"
+ properties["onChildrenInteractionSourceChange"] = onChildrenInteractionSourceChange
+ }
+}
+
+private object InteractionSourceModifierNodeTraverseKey
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
index 310d6c6..76152f8 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
@@ -357,7 +357,6 @@
}
}
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
internal fun DropdownMenuContent(
modifier: Modifier,
@@ -373,18 +372,16 @@
) {
// Menu open/close animation.
@Suppress("DEPRECATION") val transition = updateTransition(expandedState, "DropDownMenu")
-
+ // TODO Load the motionScheme tokens from the component tokens file
+ val scaleAnimationSpec = MotionSchemeKeyTokens.FastSpatial.value<Float>()
+ val alphaAnimationSpec = MotionSchemeKeyTokens.FastEffects.value<Float>()
val scale by
- transition.animateFloat(
- // TODO Load the motionScheme tokens from the component tokens file
- transitionSpec = { MotionSchemeKeyTokens.FastSpatial.value() }
- ) { expanded ->
+ transition.animateFloat(transitionSpec = { scaleAnimationSpec }) { expanded ->
if (expanded) ExpandedScaleTarget else ClosedScaleTarget
}
val alpha by
- transition.animateFloat(transitionSpec = { MotionSchemeKeyTokens.FastEffects.value() }) {
- expanded ->
+ transition.animateFloat(transitionSpec = { alphaAnimationSpec }) { expanded ->
if (expanded) ExpandedAlphaTarget else ClosedAlphaTarget
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
index 91cb8fa..e144c31 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
@@ -113,7 +113,6 @@
* sheet's window behavior.
* @param content The content to be displayed inside the bottom sheet.
*/
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@ExperimentalMaterial3Api
fun ModalBottomSheet(
@@ -429,7 +428,6 @@
initialValue = Hidden,
)
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun Scrim(color: Color, onDismissRequest: () -> Unit, visible: Boolean) {
// TODO Load the motionScheme tokens from the component tokens file
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
index f172805..d1f3576 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
@@ -168,7 +168,6 @@
* preview the item in different states. Note that if `null` is provided, interactions will still
* happen internally.
*/
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun RowScope.NavigationBarItem(
selected: Boolean,
@@ -183,13 +182,14 @@
) {
@Suppress("NAME_SHADOWING")
val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
+ // TODO Load the motionScheme tokens from the component tokens file
+ val colorAnimationSpec = MotionSchemeKeyTokens.DefaultEffects.value<Color>()
val styledIcon =
@Composable {
val iconColor by
animateColorAsState(
targetValue = colors.iconColor(selected = selected, enabled = enabled),
- // TODO Load the motionScheme tokens from the component tokens file
- animationSpec = MotionSchemeKeyTokens.DefaultEffects.value()
+ animationSpec = colorAnimationSpec
)
// If there's a label, don't have a11y services repeat the icon description.
val clearSemantics = label != null && (alwaysShowLabel || selected)
@@ -205,8 +205,7 @@
val textColor by
animateColorAsState(
targetValue = colors.textColor(selected = selected, enabled = enabled),
- // TODO Load the motionScheme tokens from the component tokens file
- animationSpec = MotionSchemeKeyTokens.DefaultEffects.value()
+ animationSpec = colorAnimationSpec
)
ProvideContentColorTextStyle(
contentColor = textColor,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
index d1e5dba..6f5dfbc 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
@@ -321,7 +321,6 @@
* @param scrimColor color of the scrim that obscures content when the drawer is open
* @param content content of the rest of the UI
*/
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ModalNavigationDrawer(
drawerContent: @Composable () -> Unit,
@@ -450,7 +449,6 @@
* @param gesturesEnabled whether or not the drawer can be interacted by gestures
* @param content content of the rest of the UI
*/
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun DismissibleNavigationDrawer(
drawerContent: @Composable () -> Unit,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
index 72bff97..76bdd73 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
@@ -373,7 +373,7 @@
) {
val isIconPositionTop = iconPosition == NavigationItemIconPosition.Top
val indicatorAnimationProgress = animateIndicatorProgressAsState(selected)
- val iconPositionProgress =
+ val iconPositionProgress by
animateFloatAsState(
targetValue = if (isIconPositionTop) 0f else 1f,
// TODO Load the motionScheme tokens from the component tokens file
@@ -382,34 +382,47 @@
// We'll always display only one label, but for the animation to be correct we need two
// separate composables that will fade in/out appropriately.
- val labelTopIconAlphaProgress = animateLabelAlphaProgressAsState(isIconPositionTop)
- val labelStartIconAlphaProgress = animateLabelAlphaProgressAsState(!isIconPositionTop)
- val labelTopIconModifier =
- if (!isIconPositionTop) {
- Modifier.graphicsLayer { alpha = labelTopIconAlphaProgress.value }
- // If this label is not being displayed, remove semantics so item's label isn't
- // announced twice.
- .clearAndSetSemantics {}
- } else {
- Modifier.graphicsLayer { alpha = labelTopIconAlphaProgress.value }
- }
+ val labelTopIconAlphaProgress by
+ animateFloatAsState(
+ targetValue = if (isIconPositionTop) 1f else 0f,
+ // TODO Load the motionScheme tokens from the component tokens file
+ animationSpec = MotionSchemeKeyTokens.DefaultEffects.value(),
+ visibilityThreshold =
+ if (isIconPositionTop) Spring.DefaultDisplacementThreshold
+ else LabelAnimationVisibilityThreshold
+ )
val labelTopIcon: @Composable (() -> Unit) = {
- Box(modifier = labelTopIconModifier) {
+ Box(
+ modifier =
+ Modifier.graphicsLayer { alpha = labelTopIconAlphaProgress }
+ .then(
+ if (isIconPositionTop) {
+ Modifier
+ } else {
+ // If this label is not being displayed, remove semantics so item's
+ // label isn't announced twice.
+ Modifier.clearAndSetSemantics {}
+ }
+ )
+ ) {
StyledLabel(selected, topIconLabelTextStyle, colors, enabled, label)
}
}
- val labelStartIconModifier =
- if (isIconPositionTop) {
- Modifier.graphicsLayer { alpha = labelStartIconAlphaProgress.value }
- // If this label is not being displayed, remove semantics so item's label isn't
- // announced twice.
- .clearAndSetSemantics {}
- } else {
- Modifier.graphicsLayer { alpha = labelStartIconAlphaProgress.value }
- }
val labelStartIcon =
@Composable {
- Box(modifier = labelStartIconModifier) {
+ Box(
+ modifier =
+ Modifier.graphicsLayer { alpha = 1f - labelTopIconAlphaProgress }
+ .then(
+ if (isIconPositionTop) {
+ // If this label is not being displayed, remove semantics so
+ // item's label isn't announced twice.
+ Modifier.clearAndSetSemantics {}
+ } else {
+ Modifier
+ }
+ )
+ ) {
StyledLabel(selected, startIconLabelTextStyle, colors, enabled, label)
}
}
@@ -440,7 +453,7 @@
indicatorAnimationProgress = { indicatorAnimationProgress.value.coerceAtLeast(0f) },
icon = iconWithBadge,
iconPosition = iconPosition,
- iconPositionProgress = { iconPositionProgress.value.coerceAtLeast(0f) },
+ iconPositionProgress = { iconPositionProgress.coerceAtLeast(0f) },
labelTopIcon = labelTopIcon,
labelStartIcon = labelStartIcon,
topIconIndicatorHorizontalPadding = topIconIndicatorHorizontalPadding,
@@ -1115,7 +1128,6 @@
)
}
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun animateIndicatorProgressAsState(selected: Boolean) =
animateFloatAsState(
@@ -1124,18 +1136,6 @@
animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value()
)
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
-@Composable
-private fun animateLabelAlphaProgressAsState(isTargetValue: Boolean) =
- animateFloatAsState(
- targetValue = if (isTargetValue) 1f else 0f,
- // TODO Load the motionScheme tokens from the component tokens file
- animationSpec = MotionSchemeKeyTokens.DefaultEffects.value(),
- visibilityThreshold =
- if (isTargetValue) Spring.DefaultDisplacementThreshold
- else LabelAnimationVisibilityThreshold
- )
-
@Composable
private fun IndicatorRipple(interactionSource: InteractionSource, indicatorShape: Shape) {
Box(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt
index b518c36..4caab04 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt
@@ -168,7 +168,6 @@
* preview the item in different states. Note that if `null` is provided, interactions will still
* happen internally.
*/
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun NavigationRailItem(
selected: Boolean,
@@ -183,13 +182,14 @@
) {
@Suppress("NAME_SHADOWING")
val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
+ // TODO Load the motionScheme tokens from the component tokens file
+ val colorAnimationSpec = MotionSchemeKeyTokens.DefaultEffects.value<Color>()
val styledIcon =
@Composable {
val iconColor by
animateColorAsState(
targetValue = colors.iconColor(selected = selected, enabled = enabled),
- // TODO Load the motionScheme tokens from the component tokens file
- animationSpec = MotionSchemeKeyTokens.DefaultEffects.value()
+ animationSpec = colorAnimationSpec
)
// If there's a label, don't have a11y services repeat the icon description.
val clearSemantics = label != null && (alwaysShowLabel || selected)
@@ -205,8 +205,7 @@
val textColor by
animateColorAsState(
targetValue = colors.textColor(selected = selected, enabled = enabled),
- // TODO Load the motionScheme tokens from the component tokens file
- animationSpec = MotionSchemeKeyTokens.DefaultEffects.value()
+ animationSpec = colorAnimationSpec
)
ProvideContentColorTextStyle(
contentColor = textColor,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt
index 9241096..84963d6 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt
@@ -71,7 +71,6 @@
* appearance or preview the radio button in different states. Note that if `null` is provided,
* interactions will still happen internally.
*/
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun RadioButton(
selected: Boolean,
@@ -222,7 +221,6 @@
* @param enabled whether the [RadioButton] is enabled
* @param selected whether the [RadioButton] is selected
*/
- @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
internal fun radioColor(enabled: Boolean, selected: Boolean): State<Color> {
val target =
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
index 476c2bf..320653d 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
@@ -22,7 +22,6 @@
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.fadeIn
import androidx.compose.animation.scaleIn
@@ -314,7 +313,6 @@
}
}
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun SegmentedButtonContent(
icon: @Composable () -> Unit,
@@ -326,7 +324,7 @@
) {
val typography = OutlinedSegmentedButtonTokens.LabelTextFont.value
// TODO Load the motionScheme tokens from the component tokens file
- val animationSpec: FiniteAnimationSpec<Int> = MotionSchemeKeyTokens.FastSpatial.value()
+ val animationSpec = MotionSchemeKeyTokens.FastSpatial.value<Int>()
ProvideTextStyle(typography) {
val scope = rememberCoroutineScope()
val measurePolicy = remember {
@@ -560,7 +558,6 @@
* @param inactiveContent typically an icon of [IconSize]. It shows only when the button is not
* checked.
*/
- @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun Icon(
active: Boolean,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnackbarHost.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnackbarHost.kt
index 01b9fdf..38b6762 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnackbarHost.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnackbarHost.kt
@@ -319,7 +319,6 @@
// TODO: to be replaced with the public customizable implementation
// it's basically tweaked nullable version of Crossfade
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun FadeInFadeOutWithScale(
current: SnackbarData?,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt
index 718a66d..8ff038a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt
@@ -31,6 +31,7 @@
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
@@ -205,6 +206,8 @@
interactionSource: MutableInteractionSource? = null,
content: @Composable () -> Unit
) {
+ @Suppress("NAME_SHADOWING")
+ val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
CompositionLocalProvider(
LocalContentColor provides contentColor,
@@ -227,7 +230,8 @@
enabled = enabled,
onClick = onClick
)
- .childSemantics(),
+ .childSemantics()
+ .interactionSourceData(interactionSource),
propagateMinConstraints = true
) {
content()
@@ -309,6 +313,8 @@
interactionSource: MutableInteractionSource? = null,
content: @Composable () -> Unit
) {
+ @Suppress("NAME_SHADOWING")
+ val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
CompositionLocalProvider(
LocalContentColor provides contentColor,
@@ -332,7 +338,8 @@
enabled = enabled,
onClick = onClick
)
- .childSemantics(),
+ .childSemantics()
+ .interactionSourceData(interactionSource),
propagateMinConstraints = true
) {
content()
@@ -414,6 +421,8 @@
interactionSource: MutableInteractionSource? = null,
content: @Composable () -> Unit
) {
+ @Suppress("NAME_SHADOWING")
+ val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
CompositionLocalProvider(
LocalContentColor provides contentColor,
@@ -437,7 +446,8 @@
enabled = enabled,
onValueChange = onCheckedChange
)
- .childSemantics(),
+ .childSemantics()
+ .interactionSourceData(interactionSource),
propagateMinConstraints = true
) {
content()
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt
index a65c503..249920ec 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt
@@ -132,7 +132,6 @@
)
}
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Suppress("ComposableLambdaParameterNaming", "ComposableLambdaParameterPosition")
private fun SwitchImpl(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt
index a6263eb..724b82a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt
@@ -269,7 +269,6 @@
* component uses [LocalContentColor] to provide an interpolated value between [activeColor] and
* [inactiveColor] depending on the animation status.
*/
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun TabTransition(
activeColor: Color,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
index b1ebe95..0b191f1 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
@@ -555,7 +555,7 @@
fun setTabPositions(positions: List<TabPosition>)
}
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TabRowImpl(
modifier: Modifier,
@@ -571,8 +571,7 @@
contentColor = contentColor
) {
// TODO Load the motionScheme tokens from the component tokens file
- val tabIndicatorAnimationSpec: FiniteAnimationSpec<Dp> =
- MotionSchemeKeyTokens.DefaultSpatial.value()
+ val tabIndicatorAnimationSpec = MotionSchemeKeyTokens.DefaultSpatial.value<Dp>()
val scope = remember {
object : TabIndicatorScope, TabPositionsHolder {
@@ -688,7 +687,7 @@
}
}
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ScrollableTabRowImpl(
selectedTabIndex: Int,
@@ -714,8 +713,7 @@
) {
val coroutineScope = rememberCoroutineScope()
// TODO Load the motionScheme tokens from the component tokens file
- val scrollAnimationSpec: FiniteAnimationSpec<Float> =
- MotionSchemeKeyTokens.DefaultSpatial.value()
+ val scrollAnimationSpec = MotionSchemeKeyTokens.DefaultSpatial.value<Float>()
val tabIndicatorAnimationSpec: FiniteAnimationSpec<Dp> =
MotionSchemeKeyTokens.DefaultSpatial.value()
val scrollableTabData =
@@ -1023,7 +1021,6 @@
}
}
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun ScrollableTabRowWithSubcomposeImpl(
selectedTabIndex: Int,
@@ -1039,8 +1036,7 @@
Surface(modifier = modifier, color = containerColor, contentColor = contentColor) {
val coroutineScope = rememberCoroutineScope()
// TODO Load the motionScheme tokens from the component tokens file
- val scrollAnimationSpec: FiniteAnimationSpec<Float> =
- MotionSchemeKeyTokens.DefaultSpatial.value()
+ val scrollAnimationSpec = MotionSchemeKeyTokens.DefaultSpatial.value<Float>()
val scrollableTabData =
remember(scrollState, coroutineScope) {
ScrollableTabData(
@@ -1285,7 +1281,6 @@
* @param currentTabPosition [TabPosition] of the currently selected tab. This is used to
* calculate the offset of the indicator this modifier is applied to, as well as its width.
*/
- @OptIn(ExperimentalMaterial3ExpressiveApi::class)
fun Modifier.tabIndicatorOffset(currentTabPosition: TabPosition): Modifier =
composed(
inspectorInfo =
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
index fc8f61f..73b4a097 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
@@ -230,7 +230,6 @@
* @param unfocusedIndicatorLineThickness thickness of the indicator line when the text field is
* not focused
*/
- @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@ExperimentalMaterial3Api
@Composable
fun Container(
@@ -1044,7 +1043,6 @@
* @param focusedBorderThickness thickness of the border when the text field is focused
* @param unfocusedBorderThickness thickness of the border when the text field is not focused
*/
- @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@ExperimentalMaterial3Api
@Composable
fun Container(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
index f341edbc..bc9c449 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
@@ -1535,7 +1535,6 @@
}
}
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
internal fun ClockFace(
state: AnalogTimePickerState,
@@ -1669,7 +1668,6 @@
)
}
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun ClockText(
modifier: Modifier,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt
index ed0c3b7..8ce3633 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt
@@ -21,11 +21,9 @@
import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.spring
import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.interaction.FocusInteraction
-import androidx.compose.foundation.interaction.HoverInteraction
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
@@ -45,7 +43,6 @@
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@@ -63,15 +60,14 @@
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
/**
* TODO link to mio page when available.
*
* Toggle button is a toggleable button that switches between primary and tonal colors depending on
- * [checked]'s value. It also morphs between the five shapes provided in [shapes] depending on the
- * state of the interaction with the toggle button as long as the five shapes provided our
+ * [checked]'s value. It also morphs between the three shapes provided in [shapes] depending on the
+ * state of the interaction with the toggle button as long as the three shapes provided our
* [CornerBasedShape]s. If a shape in [shapes] isn't a [CornerBasedShape], then toggle button will
* toggle between the [ButtonShapes] according to user interaction.
*
@@ -120,8 +116,6 @@
ToggleButtonDefaults.shapes(
ToggleButtonDefaults.shape,
ToggleButtonDefaults.pressedShape,
- ToggleButtonDefaults.hoveredShape,
- ToggleButtonDefaults.focusedShape,
ToggleButtonDefaults.checkedShape
),
colors: ToggleButtonColors = ToggleButtonDefaults.toggleButtonColors(),
@@ -136,66 +130,31 @@
val isCornerBasedShape =
shapes.shape is CornerBasedShape &&
shapes.checkedShape is CornerBasedShape &&
- shapes.pressedShape is CornerBasedShape &&
- shapes.hoveredShape is CornerBasedShape &&
- shapes.focusedShape is CornerBasedShape
+ shapes.pressedShape is CornerBasedShape
- var pressed by remember { mutableStateOf(false) }
- var hovered by remember { mutableStateOf(false) }
- var focused by remember { mutableStateOf(false) }
+ val pressed by interactionSource.collectIsPressedAsState()
val state: AnimatedShapeState? =
if (isCornerBasedShape) {
val defaultShape = shapes.shape as CornerBasedShape
val pressedShape = shapes.pressedShape as CornerBasedShape
- val hoveredShape = shapes.hoveredShape as CornerBasedShape
- val focusedShape = shapes.focusedShape as CornerBasedShape
val checkedShape = shapes.checkedShape as CornerBasedShape
remember {
AnimatedShapeState(
startShape = if (checked) checkedShape else defaultShape,
defaultShape = defaultShape,
pressedShape = pressedShape,
- hoveredShape = hoveredShape,
- focusedShape = focusedShape,
checkedShape = checkedShape,
spring(),
)
}
} else null
- LaunchedEffect(interactionSource) {
- interactionSource.interactions.collectLatest { interaction ->
- when (interaction) {
- is PressInteraction.Press -> {
- pressed = true
- }
- is HoverInteraction.Enter -> {
- hovered = true
- }
- is FocusInteraction.Focus -> {
- focused = true
- }
- is PressInteraction.Release,
- is PressInteraction.Cancel -> {
- pressed = false
- }
- is HoverInteraction.Exit -> {
- hovered = false
- }
- is FocusInteraction.Unfocus -> {
- focused = false
- }
- }
- }
- }
-
val containerColor = colors.containerColor(enabled, checked)
val contentColor = colors.contentColor(enabled, checked)
val shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp
- val buttonShape =
- shapeByInteraction(isCornerBasedShape, state, shapes, pressed, hovered, focused, checked)
+ val buttonShape = shapeByInteraction(isCornerBasedShape, state, shapes, pressed, checked)
Surface(
checked = checked,
@@ -234,8 +193,8 @@
* TODO link to mio page when available.
*
* Toggle button is a toggleable button that switches between primary and tonal colors depending on
- * [checked]'s value. It also morphs between the five shapes provided in [shapes] depending on the
- * state of the interaction with the toggle button as long as the five shapes provided our
+ * [checked]'s value. It also morphs between the three shapes provided in [shapes] depending on the
+ * state of the interaction with the toggle button as long as the three shapes provided our
* [CornerBasedShape]s. If a shape in [shapes] isn't a [CornerBasedShape], then toggle button will
* toggle between the [ButtonShapes] according to user interaction.
*
@@ -282,8 +241,6 @@
ToggleButtonDefaults.shapes(
ToggleButtonDefaults.elevatedShape,
ToggleButtonDefaults.elevatedPressedShape,
- ToggleButtonDefaults.elevatedHoveredShape,
- ToggleButtonDefaults.elevatedFocusedShape,
ToggleButtonDefaults.elevatedCheckedShape
),
colors: ToggleButtonColors = ToggleButtonDefaults.elevatedToggleButtonColors(),
@@ -311,8 +268,8 @@
* TODO link to mio page when available.
*
* Toggle button is a toggleable button that switches between primary and tonal colors depending on
- * [checked]'s value. It also morphs between the five shapes provided in [shapes] depending on the
- * state of the interaction with the toggle button as long as the five shapes provided our
+ * [checked]'s value. It also morphs between the three shapes provided in [shapes] depending on the
+ * state of the interaction with the toggle button as long as the three shapes provided our
* [CornerBasedShape]s. If a shape in [shapes] isn't a [CornerBasedShape], then toggle button will
* toggle between the [ButtonShapes] according to user interaction.
*
@@ -362,8 +319,6 @@
ToggleButtonDefaults.shapes(
ToggleButtonDefaults.tonalShape,
ToggleButtonDefaults.tonalPressedShape,
- ToggleButtonDefaults.tonalHoveredShape,
- ToggleButtonDefaults.tonalFocusedShape,
ToggleButtonDefaults.tonalCheckedShape
),
colors: ToggleButtonColors = ToggleButtonDefaults.tonalToggleButtonColors(),
@@ -391,8 +346,8 @@
* TODO link to mio page when available.
*
* Toggle button is a toggleable button that switches between primary and tonal colors depending on
- * [checked]'s value. It also morphs between the five shapes provided in [shapes] depending on the
- * state of the interaction with the toggle button as long as the five shapes provided our
+ * [checked]'s value. It also morphs between the three shapes provided in [shapes] depending on the
+ * state of the interaction with the toggle button as long as the three shapes provided our
* [CornerBasedShape]s. If a shape in [shapes] isn't a [CornerBasedShape], then toggle button will
* toggle between the [ButtonShapes] according to user interaction.
*
@@ -440,8 +395,6 @@
ToggleButtonDefaults.shapes(
ToggleButtonDefaults.outlinedShape,
ToggleButtonDefaults.outlinedPressedShape,
- ToggleButtonDefaults.outlinedHoveredShape,
- ToggleButtonDefaults.outlinedFocusedShape,
ToggleButtonDefaults.outlinedCheckedShape
),
colors: ToggleButtonColors = ToggleButtonDefaults.outlinedToggleButtonColors(),
@@ -506,17 +459,10 @@
*
* @param shape the unchecked shape for [ButtonShapes]
* @param pressedShape the unchecked shape for [ButtonShapes]
- * @param hoverShape the unchecked shape for [ButtonShapes]
- * @param focusShape the unchecked shape for [ButtonShapes]
* @param checkedShape the unchecked shape for [ButtonShapes]
*/
- fun shapes(
- shape: Shape,
- pressedShape: Shape,
- hoverShape: Shape,
- focusShape: Shape,
- checkedShape: Shape
- ): ButtonShapes = ButtonShapes(shape, pressedShape, hoverShape, focusShape, checkedShape)
+ fun shapes(shape: Shape, pressedShape: Shape, checkedShape: Shape): ButtonShapes =
+ ButtonShapes(shape, pressedShape, checkedShape)
/** A round shape that can be used for all [ToggleButton]s and its variants */
val roundShape: Shape
@@ -554,30 +500,6 @@
/** The default pressed shape for [OutlinedToggleButton] */
val outlinedPressedShape: Shape = RoundedCornerShape(6.dp)
- /** The default hovered shape for [ToggleButton] */
- val hoveredShape: Shape = RoundedCornerShape(6.dp)
-
- /** The default hovered shape for [ElevatedToggleButton] */
- val elevatedHoveredShape: Shape = RoundedCornerShape(6.dp)
-
- /** The default hovered shape for [TonalToggleButton] */
- val tonalHoveredShape: Shape = RoundedCornerShape(6.dp)
-
- /** The default hovered shape for [OutlinedToggleButton] */
- val outlinedHoveredShape: Shape = RoundedCornerShape(6.dp)
-
- /** The default focused shape for [ToggleButton] */
- val focusedShape: Shape = RoundedCornerShape(6.dp)
-
- /** The default focused shape for [ElevatedToggleButton] */
- val elevatedFocusedShape: Shape = RoundedCornerShape(6.dp)
-
- /** The default focused shape for [TonalToggleButton] */
- val tonalFocusedShape: Shape = RoundedCornerShape(6.dp)
-
- /** The default focused shape for [OutlinedToggleButton] */
- val outlinedFocusedShape: Shape = RoundedCornerShape(6.dp)
-
// TODO: Change this to the new ButtonSmallTokens.SelectedShape when available
/** The default checked shape for [ToggleButton] */
val checkedShape: Shape
@@ -939,17 +861,9 @@
*
* @property shape is the unchecked shape.
* @property pressedShape is the pressed shape.
- * @property hoveredShape is the hovered shape.
- * @property focusedShape is the focused shape.
* @property checkedShape is the checked shape.
*/
-data class ButtonShapes(
- val shape: Shape,
- val pressedShape: Shape,
- val hoveredShape: Shape,
- val focusedShape: Shape,
- val checkedShape: Shape
-)
+data class ButtonShapes(val shape: Shape, val pressedShape: Shape, val checkedShape: Shape)
@Composable
private fun shapeByInteraction(
@@ -957,19 +871,13 @@
state: AnimatedShapeState?,
shapes: ButtonShapes,
pressed: Boolean,
- hovered: Boolean,
- focused: Boolean,
checked: Boolean
): Shape {
return if (isCornerBasedShape) {
if (state != null) {
- LaunchedEffect(pressed, hovered, focused, checked) {
+ LaunchedEffect(pressed, checked) {
if (pressed) {
state.animateToPressed()
- } else if (hovered) {
- state.animateToHovered()
- } else if (focused) {
- state.animateToFocused()
} else if (checked) {
state.animateToChecked()
} else {
@@ -982,10 +890,6 @@
}
} else if (pressed) {
shapes.pressedShape
- } else if (hovered) {
- shapes.hoveredShape
- } else if (focused) {
- shapes.focusedShape
} else if (checked) {
shapes.checkedShape
} else {
@@ -997,6 +901,7 @@
private fun rememberAnimatedShape(state: AnimatedShapeState): Shape {
val density = LocalDensity.current
state.density = density
+
return remember(density) {
object : Shape {
@@ -1028,8 +933,6 @@
val startShape: CornerBasedShape,
val defaultShape: CornerBasedShape,
val pressedShape: CornerBasedShape,
- val hoveredShape: CornerBasedShape,
- val focusedShape: CornerBasedShape,
val checkedShape: CornerBasedShape,
val spec: SpringSpec<Float>,
) {
@@ -1063,10 +966,6 @@
suspend fun animateToDefault() = animateToShape(defaultShape)
- suspend fun animateToHovered() = animateToShape(hoveredShape)
-
- suspend fun animateToFocused() = animateToShape(focusedShape)
-
private suspend fun animateToShape(shape: CornerBasedShape) = coroutineScope {
launch { topStart?.animateTo(shape.topStart.toPx(size, density), spec) }
launch { topEnd?.animateTo(shape.topEnd.toPx(size, density), spec) }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
index e176caf..e8b1726 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
@@ -16,7 +16,6 @@
package androidx.compose.material3
-import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.animateFloat
@@ -571,7 +570,6 @@
}
}
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
internal fun Modifier.animateTooltip(transition: Transition<Boolean>): Modifier =
composed(
inspectorInfo =
@@ -581,10 +579,8 @@
}
) {
// TODO Load the motionScheme tokens from the component tokens file
- val inOutScaleAnimationSpec: FiniteAnimationSpec<Float> =
- MotionSchemeKeyTokens.FastSpatial.value()
- val inOutAlphaAnimationSpec: FiniteAnimationSpec<Float> =
- MotionSchemeKeyTokens.FastEffects.value()
+ val inOutScaleAnimationSpec = MotionSchemeKeyTokens.FastSpatial.value<Float>()
+ val inOutAlphaAnimationSpec = MotionSchemeKeyTokens.FastEffects.value<Float>()
val scale by
transition.animateFloat(
transitionSpec = { inOutScaleAnimationSpec },
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt
index c90d013..f3e8264 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt
@@ -37,7 +37,9 @@
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material3.internal.DraggableAnchors
+import androidx.compose.material3.internal.Strings
import androidx.compose.material3.internal.draggableAnchors
+import androidx.compose.material3.internal.getString
import androidx.compose.material3.internal.systemBarsForVisualComponents
import androidx.compose.material3.tokens.ColorSchemeKeyTokens
import androidx.compose.material3.tokens.MotionSchemeKeyTokens
@@ -70,6 +72,7 @@
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.paneTitle
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
@@ -178,29 +181,27 @@
LocalMinimumInteractiveComponentSize.current
}
+ // TODO: Load the motionScheme tokens from the component tokens file.
+ val animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value<Dp>()
val minWidth by
animateDpAsState(
targetValue = if (!expanded) CollapsedRailWidth else ExpandedRailMinWidth,
- // TODO: Load the motionScheme tokens from the component tokens file.
- animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value()
+ animationSpec = animationSpec
)
val widthFullRange by
animateDpAsState(
targetValue = if (!expanded) CollapsedRailWidth else ExpandedRailMaxWidth,
- // TODO: Load the motionScheme tokens from the component tokens file.
- animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value()
+ animationSpec = animationSpec
)
val itemVerticalSpacedBy by
animateDpAsState(
targetValue = if (!expanded) VerticalPaddingBetweenTopIconItems else 0.dp,
- // TODO: Load the motionScheme tokens from the component tokens file.
- animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value()
+ animationSpec = animationSpec
)
val itemMarginStart by
animateDpAsState(
targetValue = if (!expanded) 0.dp else ExpandedRailHorizontalItemPadding,
- // TODO: Load the motionScheme tokens from the component tokens file.
- animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value()
+ animationSpec = animationSpec
)
Surface(
@@ -787,12 +788,14 @@
content: @Composable () -> Unit
) {
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
+ val railPaneTitle = getString(string = Strings.WideNavigationRailPaneTitle)
Box(
modifier =
modifier
.fillMaxHeight()
.widthIn(max = openModalRailMaxWidth)
+ .semantics { paneTitle = railPaneTitle }
.graphicsLayer {
// TODO: Implement predictive back behavior.
}
@@ -839,8 +842,7 @@
animationSpec = MotionSchemeKeyTokens.DefaultEffects.value()
)
var dismiss by remember { mutableStateOf(false) }
- // TODO: Add this string in Strings.
- val closeModalRail = "Close modal navigation rail."
+ val closeModalRail = getString(Strings.CloseRail)
val dismissModalRail =
if (visible) {
Modifier.pointerInput(onDismissRequest) { detectTapGestures { dismiss = true } }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRailState.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRailState.kt
index 59627d5..865f380 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRailState.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRailState.kt
@@ -185,7 +185,7 @@
): ModalExpandedNavigationRailState {
val density = LocalDensity.current
// TODO: Load the motionScheme tokens from the component tokens file.
- val animationSpec: AnimationSpec<Float> = MotionSchemeKeyTokens.DefaultSpatial.value()
+ val animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value<Float>()
return rememberSaveable(
saver = ModalExpandedNavigationRailState.Saver(density, animationSpec, confirmValueChange)
) {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/Strings.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/Strings.kt
index d0e4575..db4d0be 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/Strings.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/Strings.kt
@@ -27,6 +27,7 @@
companion object {
val NavigationMenu: Strings
val CloseDrawer: Strings
+ val CloseRail: Strings
val CloseSheet: Strings
val DefaultErrorMessage: Strings
val ExposedDropdownMenu: Strings
@@ -90,6 +91,7 @@
val TimePickerHourTextField: Strings
val TimePickerMinuteTextField: Strings
val TooltipPaneDescription: Strings
+ val WideNavigationRailPaneTitle: Strings
}
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/TextFieldImpl.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/TextFieldImpl.kt
index 1ad1dcc..cb141ccd 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/TextFieldImpl.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/TextFieldImpl.kt
@@ -27,7 +27,6 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextFieldLayout
@@ -427,7 +426,6 @@
)
}
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
internal fun animateBorderStrokeAsState(
enabled: Boolean,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/pulltorefresh/PullToRefresh.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/pulltorefresh/PullToRefresh.kt
index 9e697a1..73196bb 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/pulltorefresh/PullToRefresh.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/pulltorefresh/PullToRefresh.kt
@@ -479,7 +479,6 @@
* release
*/
@Suppress("DEPRECATION")
- @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun Indicator(
state: PullToRefreshState,
@@ -672,7 +671,6 @@
}
/** The default pull indicator for [PullToRefreshBox] */
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun CircularArrowProgressIndicator(
progress: () -> Float,
diff --git a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.native.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ButtonGroupSmallTokens.kt
similarity index 65%
rename from kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.native.kt
rename to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ButtonGroupSmallTokens.kt
index 0e510cf..ec51dcd 100644
--- a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.native.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ButtonGroupSmallTokens.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,12 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+// VERSION: v0_11_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
-package androidx.kruth
+package androidx.compose.material3.tokens
-internal actual interface PlatformStandardSubjectBuilder
+import androidx.compose.ui.unit.dp
-internal actual class PlatformStandardSubjectBuilderImpl
-actual constructor(
- metadata: FailureMetadata,
-) : PlatformStandardSubjectBuilder
+internal object ButtonGroupSmallTokens {
+ val BetweenSpace = 12.0.dp
+ val ContainerHeight = 40.0.dp
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/LargeIconButtonTokens.kt
similarity index 62%
copy from compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
copy to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/LargeIconButtonTokens.kt
index aa3dc18..040a520 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/LargeIconButtonTokens.kt
@@ -13,25 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_4_0
+// VERSION: v0_9_0
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
import androidx.compose.ui.unit.dp
-internal object IconButtonSmallTokens {
- val ContainerHeight = 40.0.dp
+internal object LargeIconButtonTokens {
+ val ContainerHeight = 96.0.dp
val ContainerShapeRound = ShapeKeyTokens.CornerFull
- val ContainerShapeSquare = ShapeKeyTokens.CornerMedium
- val IconSize = 24.0.dp
- val NarrowLeadingSpace = 4.0.dp
- val NarrowTrailingSpace = 4.0.dp
- val OutlinedOutlineWidth = 1.0.dp
+ val ContainerShapeSquare = ShapeKeyTokens.CornerExtraLarge
+ val UniformLeadingSpace = 32.0.dp
+ val UniformTrailingSpace = 32.0.dp
+ val IconSize = 32.0.dp
+ val NarrowLeadingSpace = 16.0.dp
+ val NarrowTrailingSpace = 16.0.dp
+ val OutlinedOutlineWidth = 2.0.dp
val PressedContainerCornerSizeMultiplierPercent = 50.0f
- val SelectedPressedContainerShape = ShapeKeyTokens.CornerMedium
- val UniformLeadingSpace = 8.0.dp
- val UniformTrailingSpace = 8.0.dp
- val WideLeadingSpace = 14.0.dp
- val WideTrailingSpace = 14.0.dp
+ val SelectedPressedContainerShape = ShapeKeyTokens.CornerExtraLarge
+ val WideLeadingSpace = 48.0.dp
+ val WideTrailingSpace = 48.0.dp
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/MediumIconButtonTokens.kt
similarity index 67%
copy from compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
copy to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/MediumIconButtonTokens.kt
index aa3dc18..310d016 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/MediumIconButtonTokens.kt
@@ -13,25 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_4_0
+// VERSION: v0_9_0
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
import androidx.compose.ui.unit.dp
-internal object IconButtonSmallTokens {
- val ContainerHeight = 40.0.dp
+internal object MediumIconButtonTokens {
+ val ContainerHeight = 56.0.dp
val ContainerShapeRound = ShapeKeyTokens.CornerFull
- val ContainerShapeSquare = ShapeKeyTokens.CornerMedium
+ val ContainerShapeSquare = ShapeKeyTokens.CornerLarge
val IconSize = 24.0.dp
- val NarrowLeadingSpace = 4.0.dp
- val NarrowTrailingSpace = 4.0.dp
+ val NarrowLeadingSpace = 10.0.dp
+ val NarrowTrailingSpace = 10.0.dp
val OutlinedOutlineWidth = 1.0.dp
val PressedContainerCornerSizeMultiplierPercent = 50.0f
- val SelectedPressedContainerShape = ShapeKeyTokens.CornerMedium
- val UniformLeadingSpace = 8.0.dp
- val UniformTrailingSpace = 8.0.dp
- val WideLeadingSpace = 14.0.dp
- val WideTrailingSpace = 14.0.dp
+ val SelectedPressedContainerShape = ShapeKeyTokens.CornerLarge
+ val UniformLeadingSpace = 16.0.dp
+ val UniformTrailingSpace = 16.0.dp
+ val WideLeadingSpace = 24.0.dp
+ val WideTrailingSpace = 24.0.dp
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SmallIconButtonTokens.kt
similarity index 95%
rename from compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
rename to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SmallIconButtonTokens.kt
index aa3dc18..e2bda5b 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SmallIconButtonTokens.kt
@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_4_0
+// VERSION: v0_9_0
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
import androidx.compose.ui.unit.dp
-internal object IconButtonSmallTokens {
+internal object SmallIconButtonTokens {
val ContainerHeight = 40.0.dp
val ContainerShapeRound = ShapeKeyTokens.CornerFull
val ContainerShapeSquare = ShapeKeyTokens.CornerMedium
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/XLargeIconButtonTokens.kt
similarity index 62%
copy from compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
copy to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/XLargeIconButtonTokens.kt
index aa3dc18..372522d 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/XLargeIconButtonTokens.kt
@@ -13,25 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_4_0
+// VERSION: v0_9_0
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
import androidx.compose.ui.unit.dp
-internal object IconButtonSmallTokens {
- val ContainerHeight = 40.0.dp
+internal object XLargeIconButtonTokens {
+ val ContainerHeight = 136.0.dp
val ContainerShapeRound = ShapeKeyTokens.CornerFull
- val ContainerShapeSquare = ShapeKeyTokens.CornerMedium
- val IconSize = 24.0.dp
- val NarrowLeadingSpace = 4.0.dp
- val NarrowTrailingSpace = 4.0.dp
- val OutlinedOutlineWidth = 1.0.dp
+ val ContainerShapeSquare = ShapeKeyTokens.CornerExtraLarge
+ val IconSize = 40.0.dp
+ val NarrowLeadingSpace = 32.0.dp
+ val NarrowTrailingSpace = 32.0.dp
+ val OutlinedOutlineWidth = 3.0.dp
val PressedContainerCornerSizeMultiplierPercent = 50.0f
- val SelectedPressedContainerShape = ShapeKeyTokens.CornerMedium
- val UniformLeadingSpace = 8.0.dp
- val UniformTrailingSpace = 8.0.dp
- val WideLeadingSpace = 14.0.dp
- val WideTrailingSpace = 14.0.dp
+ val SelectedPressedContainerShape = ShapeKeyTokens.CornerExtraLarge
+ val UniformLeadingSpace = 48.0.dp
+ val UniformTrailingSpace = 48.0.dp
+ val WideLeadingSpace = 72.0.dp
+ val WideTrailingSpace = 72.0.dp
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/XSmallIconButtonTokens.kt
similarity index 80%
copy from compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
copy to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/XSmallIconButtonTokens.kt
index aa3dc18..75ab01b 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/XSmallIconButtonTokens.kt
@@ -13,25 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_4_0
+// VERSION: v0_9_0
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
import androidx.compose.ui.unit.dp
-internal object IconButtonSmallTokens {
- val ContainerHeight = 40.0.dp
+internal object XSmallIconButtonTokens {
+ val ContainerHeight = 32.0.dp
val ContainerShapeRound = ShapeKeyTokens.CornerFull
val ContainerShapeSquare = ShapeKeyTokens.CornerMedium
- val IconSize = 24.0.dp
+ val IconSize = 20.0.dp
val NarrowLeadingSpace = 4.0.dp
val NarrowTrailingSpace = 4.0.dp
val OutlinedOutlineWidth = 1.0.dp
val PressedContainerCornerSizeMultiplierPercent = 50.0f
val SelectedPressedContainerShape = ShapeKeyTokens.CornerMedium
- val UniformLeadingSpace = 8.0.dp
- val UniformTrailingSpace = 8.0.dp
- val WideLeadingSpace = 14.0.dp
- val WideTrailingSpace = 14.0.dp
+ val UniformLeadingSpace = 6.0.dp
+ val UniformTrailingSpace = 6.0.dp
+ val WideLeadingSpace = 10.0.dp
+ val WideTrailingSpace = 10.0.dp
}
diff --git a/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/internal/Strings.commonStubs.kt b/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/internal/Strings.commonStubs.kt
index a2c5526..aaf4893 100644
--- a/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/internal/Strings.commonStubs.kt
+++ b/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/internal/Strings.commonStubs.kt
@@ -32,6 +32,7 @@
actual companion object {
actual val NavigationMenu: Strings = implementedInJetBrainsFork()
actual val CloseDrawer: Strings = implementedInJetBrainsFork()
+ actual val CloseRail: Strings = implementedInJetBrainsFork()
actual val CloseSheet: Strings = implementedInJetBrainsFork()
actual val DefaultErrorMessage: Strings = implementedInJetBrainsFork()
actual val SliderRangeStart: Strings = implementedInJetBrainsFork()
@@ -95,6 +96,7 @@
actual val TooltipPaneDescription: Strings = implementedInJetBrainsFork()
actual val ExposedDropdownMenu: Strings = implementedInJetBrainsFork()
actual val ToggleDropdownMenu: Strings = implementedInJetBrainsFork()
+ actual val WideNavigationRailPaneTitle: Strings = implementedInJetBrainsFork()
}
}
diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle
index a82f5ac..fc61115 100644
--- a/compose/runtime/runtime/build.gradle
+++ b/compose/runtime/runtime/build.gradle
@@ -43,7 +43,7 @@
dependencies {
implementation(libs.kotlinStdlibCommon)
implementation(libs.kotlinCoroutinesCore)
- implementation(project(":collection:collection"))
+ implementation("androidx.collection:collection:1.4.2")
}
}
diff --git a/compose/runtime/runtime/compose-runtime-benchmark/build.gradle b/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
index 52aa010..65f7a91 100644
--- a/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
+++ b/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
@@ -24,9 +24,6 @@
android {
compileSdk 35
namespace "androidx.compose.runtime.benchmark"
-
- // Experiment to observe effect of minification on noise, see b/354264743
- buildTypes.release.androidTest.enableMinification = true
}
dependencies {
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
index a4936f1..c7df5e5 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
@@ -709,7 +709,7 @@
internal data class DrawParams(
var density: Density = DefaultDensity,
var layoutDirection: LayoutDirection = LayoutDirection.Ltr,
- var canvas: Canvas = EmptyCanvas(),
+ var canvas: Canvas = EmptyCanvas,
var size: Size = Size.Zero
)
}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawContext.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawContext.kt
index 28dccc7..8f34f12 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawContext.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawContext.kt
@@ -47,7 +47,7 @@
/** The target canvas to issue drawing commands */
var canvas: Canvas
- get() = EmptyCanvas()
+ get() = EmptyCanvas
set(_) {}
/** The controller for issuing transformations to the drawing environment */
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt
index 8f41a31..6ca81a8 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt
@@ -36,8 +36,7 @@
* thrown at runtime. During normal use, the canvas used within [DrawScope] is consuming a valid
* Canvas that draws content into a valid destination
*/
-internal class EmptyCanvas : Canvas {
-
+internal object EmptyCanvas : Canvas {
override fun save() {
throw UnsupportedOperationException()
}
diff --git a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/injectionscope/touch/MoveWithHistoryTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/injectionscope/touch/MoveWithHistoryTest.kt
index e0e78f2..58ded89 100644
--- a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/injectionscope/touch/MoveWithHistoryTest.kt
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/injectionscope/touch/MoveWithHistoryTest.kt
@@ -26,8 +26,10 @@
import androidx.compose.runtime.Composable
import androidx.compose.testutils.WithTouchSlop
import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.util.VelocityTrackerAddPointsFix
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.ExperimentalTestApi
@@ -44,6 +46,7 @@
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalComposeUiApi::class)
@MediumTest
@RunWith(AndroidJUnit4::class)
class MoveWithHistoryTest {
@@ -114,9 +117,18 @@
val from = topCenter + Offset(0f, 120f)
val to = topCenter + Offset(0f, 100f)
- val historicalTimes = listOf(-16L, -12L, -8L)
+ val historicalTimes =
+ if (VelocityTrackerAddPointsFix) {
+ listOf(-16L, -12L, -8L)
+ } else {
+ listOf(-16L, -8L)
+ }
val historicalCoordinates =
- listOf(to + Offset(0f, 70f), to + Offset(0f, 55f), to + Offset(0f, 35f))
+ if (VelocityTrackerAddPointsFix) {
+ listOf(to + Offset(0f, 70f), to + Offset(0f, 55f), to + Offset(0f, 35f))
+ } else {
+ listOf(to + Offset(0f, 70f), to + Offset(0f, 35f))
+ }
val delayMillis = 100L
down(from)
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/TextLayoutCacheTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/TextLayoutCacheTest.kt
index ea391f4..819b397 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/TextLayoutCacheTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/TextLayoutCacheTest.kt
@@ -32,6 +32,7 @@
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,6 +55,39 @@
}
@Test
+ fun capacity_one_shouldEvictTheCache_forEveryDifferentLayoutInput() {
+ val textLayoutCache = TextLayoutCache(1)
+
+ val input1 =
+ textLayoutInput(text = AnnotatedString("W"), style = TextStyle(color = Color.Red))
+ textLayoutCache.put(input1, layoutText(input1))
+
+ val input2 =
+ textLayoutInput(text = AnnotatedString("Wo"), style = TextStyle(color = Color.Red))
+ textLayoutCache.put(input2, layoutText(input2))
+ assertThat(textLayoutCache.get(input2)).isNotNull()
+ assertThat(textLayoutCache.get(input1)).isNull()
+
+ val input3 =
+ textLayoutInput(text = AnnotatedString("Wor"), style = TextStyle(color = Color.Red))
+ textLayoutCache.put(input3, layoutText(input3))
+ assertThat(textLayoutCache.get(input3)).isNotNull()
+ assertThat(textLayoutCache.get(input2)).isNull()
+
+ val input4 =
+ textLayoutInput(text = AnnotatedString("Worl"), style = TextStyle(color = Color.Red))
+ textLayoutCache.put(input4, layoutText(input4))
+ assertThat(textLayoutCache.get(input4)).isNotNull()
+ assertThat(textLayoutCache.get(input3)).isNull()
+
+ val input5 =
+ textLayoutInput(text = AnnotatedString("World"), style = TextStyle(color = Color.Red))
+ textLayoutCache.put(input5, layoutText(input5))
+ assertThat(textLayoutCache.get(input5)).isNotNull()
+ assertThat(textLayoutCache.get(input4)).isNull()
+ }
+
+ @Test
fun exactInput_shouldReturnTheSameResult() {
val textLayoutCache = TextLayoutCache(16)
val textLayoutInput =
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt
index f3fa771..405ca04 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt
@@ -359,10 +359,30 @@
* @throws IllegalArgumentException if capacity is not a positive integer.
*/
internal class TextLayoutCache(capacity: Int = DefaultCacheSize) {
- private val cache = SieveCache<CacheTextLayoutInput, TextLayoutResult>(capacity, capacity)
+ // Do not allocate an LRU cache if the size is just 1.
+ private val cache: SieveCache<CacheTextLayoutInput, TextLayoutResult>? =
+ if (capacity != 1) {
+ // 0 or negative cache size is also handled by SieveCache.
+ SieveCache(capacity, capacity)
+ } else {
+ null
+ }
+
+ private var singleSizeCacheInput: CacheTextLayoutInput? = null
+ private var singleSizeCacheResult: TextLayoutResult? = null
fun get(key: TextLayoutInput): TextLayoutResult? {
- val resultFromCache = cache[CacheTextLayoutInput(key)] ?: return null
+ val cacheKey = CacheTextLayoutInput(key)
+ val resultFromCache =
+ if (cache != null) {
+ cache[cacheKey]
+ } else if (singleSizeCacheInput == cacheKey) {
+ singleSizeCacheResult
+ } else {
+ return null
+ }
+
+ if (resultFromCache == null) return null
if (resultFromCache.multiParagraph.intrinsics.hasStaleResolvedFonts) {
// one of the resolved fonts has updated, and this MeasuredText is no longer valid for
@@ -373,12 +393,13 @@
return resultFromCache
}
- fun put(key: TextLayoutInput, value: TextLayoutResult): TextLayoutResult? {
- return cache.put(CacheTextLayoutInput(key), value)
- }
-
- fun remove(key: TextLayoutInput): TextLayoutResult? {
- return cache.remove(CacheTextLayoutInput(key))
+ fun put(key: TextLayoutInput, value: TextLayoutResult) {
+ if (cache != null) {
+ cache.put(CacheTextLayoutInput(key), value)
+ } else {
+ singleSizeCacheInput = CacheTextLayoutInput(key)
+ singleSizeCacheResult = value
+ }
}
}
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 3dfd54f..e88ca9c 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2052,8 +2052,11 @@
public final class VelocityTrackerKt {
method public static void addPointerInputChange(androidx.compose.ui.input.pointer.util.VelocityTracker, androidx.compose.ui.input.pointer.PointerInputChange event);
+ method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static boolean getVelocityTrackerAddPointsFix();
method @SuppressCompatibility @androidx.compose.ui.input.pointer.util.ExperimentalVelocityTrackerApi public static boolean getVelocityTrackerStrategyUseImpulse();
+ method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static void setVelocityTrackerAddPointsFix(boolean);
method @SuppressCompatibility @androidx.compose.ui.input.pointer.util.ExperimentalVelocityTrackerApi public static void setVelocityTrackerStrategyUseImpulse(boolean);
+ property @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static final boolean VelocityTrackerAddPointsFix;
property @SuppressCompatibility @androidx.compose.ui.input.pointer.util.ExperimentalVelocityTrackerApi public static final boolean VelocityTrackerStrategyUseImpulse;
}
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 7929b25..3c279a0 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2052,8 +2052,11 @@
public final class VelocityTrackerKt {
method public static void addPointerInputChange(androidx.compose.ui.input.pointer.util.VelocityTracker, androidx.compose.ui.input.pointer.PointerInputChange event);
+ method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static boolean getVelocityTrackerAddPointsFix();
method @SuppressCompatibility @androidx.compose.ui.input.pointer.util.ExperimentalVelocityTrackerApi public static boolean getVelocityTrackerStrategyUseImpulse();
+ method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static void setVelocityTrackerAddPointsFix(boolean);
method @SuppressCompatibility @androidx.compose.ui.input.pointer.util.ExperimentalVelocityTrackerApi public static void setVelocityTrackerStrategyUseImpulse(boolean);
+ property @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static final boolean VelocityTrackerAddPointsFix;
property @SuppressCompatibility @androidx.compose.ui.input.pointer.util.ExperimentalVelocityTrackerApi public static final boolean VelocityTrackerStrategyUseImpulse;
}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingListParityTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingListParityTest.kt
index e3dcf8d..8d0b0ee 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingListParityTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingListParityTest.kt
@@ -33,9 +33,11 @@
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.background
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.util.VelocityTrackerAddPointsFix
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.tests.R
@@ -70,10 +72,12 @@
private var latestComposeVelocity = 0f
private var latestRVState = -1
+ @OptIn(ExperimentalComposeUiApi::class)
@Before
fun setUp() {
layoutManager = null
latestComposeVelocity = 0f
+ VelocityTrackerAddPointsFix = true
}
@Test
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingParityTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingParityTest.kt
index 39354a7..4fef305 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingParityTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingParityTest.kt
@@ -33,6 +33,7 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.background
import androidx.compose.ui.graphics.Color
@@ -43,6 +44,7 @@
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChangedIgnoreConsumed
+import androidx.compose.ui.input.pointer.util.VelocityTrackerAddPointsFix
import androidx.compose.ui.input.pointer.util.addPointerInputChange
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalViewConfiguration
@@ -75,6 +77,7 @@
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalComposeUiApi::class)
@MediumTest
@RunWith(AndroidJUnit4::class)
class VelocityTrackingParityTest {
@@ -92,6 +95,7 @@
@Before
fun setUp() {
latestComposeVelocity = Velocity.Zero
+ VelocityTrackerAddPointsFix = true
}
fun tearDown() {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
index 9a2afc9..5e4f56a 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
@@ -40,6 +40,8 @@
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.graphics.layer.setOutline
import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.internal.checkPreconditionNotNull
+import androidx.compose.ui.internal.requirePrecondition
import androidx.compose.ui.layout.GraphicLayerInfo
import androidx.compose.ui.node.OwnedLayer
import androidx.compose.ui.unit.Density
@@ -351,10 +353,12 @@
invalidateParentLayer: () -> Unit
) {
val context =
- requireNotNull(context) {
+ checkPreconditionNotNull(context) {
"currently reuse is only supported when we manage the layer lifecycle"
}
- require(graphicsLayer.isReleased) { "layer should have been released before reuse" }
+ requirePrecondition(graphicsLayer.isReleased) {
+ "layer should have been released before reuse"
+ }
// recreate a layer
graphicsLayer = context.createGraphicsLayer()
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
index 2fc6e18..c934641 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
@@ -16,6 +16,7 @@
package androidx.compose.ui.input.pointer.util
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed
@@ -357,7 +358,52 @@
*
* @param event Pointer change to track.
*/
+@OptIn(ExperimentalComposeUiApi::class)
fun VelocityTracker.addPointerInputChange(event: PointerInputChange) {
+ if (VelocityTrackerAddPointsFix) {
+ addPointerInputChangeWithFix(event)
+ } else {
+ addPointerInputChangeLegacy(event)
+ }
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+private fun VelocityTracker.addPointerInputChangeLegacy(event: PointerInputChange) {
+
+ // Register down event as the starting point for the accumulator
+ if (event.changedToDownIgnoreConsumed()) {
+ currentPointerPositionAccumulator = event.position
+ resetTracking()
+ }
+
+ // To calculate delta, for each step we want to do currentPosition - previousPosition.
+ // Initially the previous position is the previous position of the current event
+ var previousPointerPosition = event.previousPosition
+ @OptIn(ExperimentalComposeUiApi::class)
+ event.historical.fastForEach {
+ // Historical data happens within event.position and event.previousPosition
+ // That means, event.previousPosition < historical data < event.position
+ // Initially, the first delta will happen between the previousPosition and
+ // the first position in historical delta. For subsequent historical data, the
+ // deltas happen between themselves. That's why we need to update previousPointerPosition
+ // everytime.
+ val historicalDelta = it.position - previousPointerPosition
+ previousPointerPosition = it.position
+
+ // Update the current position with the historical delta and add it to the tracker
+ currentPointerPositionAccumulator += historicalDelta
+ addPosition(it.uptimeMillis, currentPointerPositionAccumulator)
+ }
+
+ // For the last position in the event
+ // If there's historical data, the delta is event.position - lastHistoricalPoint
+ // If there's no historical data, the delta is event.position - event.previousPosition
+ val delta = event.position - previousPointerPosition
+ currentPointerPositionAccumulator += delta
+ addPosition(event.uptimeMillis, currentPointerPositionAccumulator)
+}
+
+private fun VelocityTracker.addPointerInputChangeWithFix(event: PointerInputChange) {
// If this is ACTION_DOWN: Reset the tracking.
if (event.changedToDownIgnoreConsumed()) {
resetTracking()
@@ -624,6 +670,21 @@
}
/**
+ * A flag to indicate that we'll use the fix of how we add points to the velocity tracker.
+ *
+ * This is an experiment flag and will be removed once the experiments with the fix a finished. The
+ * final goal is that we will use the true path once the flag is removed. If you find any issues
+ * with the new fix, flip this flag to false to confirm they are newly introduced then file a bug.
+ * Tracking bug: (b/318621681)
+ */
+@Suppress("GetterSetterNames", "OPT_IN_MARKER_ON_WRONG_TARGET")
+@get:Suppress("GetterSetterNames")
+@get:ExperimentalComposeUiApi
+@set:ExperimentalComposeUiApi
+@ExperimentalComposeUiApi
+var VelocityTrackerAddPointsFix: Boolean = true
+
+/**
* Selecting flag to enable impulse strategy for the velocity trackers. This is an experiment flag
* and will be removed once the experiments with the fix a finished. The final goal is that we will
* use the true path once the flag is removed. If you find any issues with the new fix, flip this
diff --git a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt
index 2f07d423..c58bad7 100644
--- a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt
+++ b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt
@@ -120,37 +120,6 @@
/** IME visibility is only reliable on API 23+, where we have access to the root WindowInsets */
@SdkSuppress(minSdkVersion = 23)
- @Test
- public fun do_not_show_IME_if_TextView_in_dialog_not_focused() {
- val dialog =
- scenario.withActivity {
- object : Dialog(this) {
- override fun onAttachedToWindow() {
- super.onAttachedToWindow()
- WindowCompat.setDecorFitsSystemWindows(window!!, false)
- }
- }
- .apply { setContentView(R.layout.insets_compat_activity) }
- }
-
- val editText = dialog.findViewById<TextView>(R.id.edittext)
-
- // We hide the edit text to ensure it won't be automatically focused
- scenario.onActivity {
- dialog.show()
- editText.visibility = View.GONE
- assertThat(editText.isFocused, `is`(false))
- }
-
- val type = WindowInsetsCompat.Type.ime()
- scenario.onActivity {
- WindowCompat.getInsetsController(dialog.window!!, editText).show(type)
- }
- container.assertInsetsVisibility(type, false)
- }
-
- /** IME visibility is only reliable on API 23+, where we have access to the root WindowInsets */
- @SdkSuppress(minSdkVersion = 23)
@Ignore("b/294556594")
@Test
fun show_IME_fromEditText_in_dialog() {
diff --git a/core/core/src/main/java/androidx/core/graphics/TypefaceCompat.java b/core/core/src/main/java/androidx/core/graphics/TypefaceCompat.java
index b165f5c..fcf65ad 100644
--- a/core/core/src/main/java/androidx/core/graphics/TypefaceCompat.java
+++ b/core/core/src/main/java/androidx/core/graphics/TypefaceCompat.java
@@ -78,7 +78,8 @@
}
/**
- * Cache for Typeface objects dynamically loaded from assets.
+ * Cache for Typeface objects dynamically loaded from assets,
+ * keyed by {@link #createResourceUid(Resources, int, String, int, int)}
*/
private static final LruCache<String, Typeface> sTypefaceCache = new LruCache<>(16);
diff --git a/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java b/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java
index 761c25b..509de8d 100644
--- a/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java
+++ b/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java
@@ -58,6 +58,9 @@
private FontRequestWorker() {}
+ /**
+ * Keyed by {@link #createCacheId(List, int)}
+ */
static final LruCache<String, Typeface> sTypefaceCache = new LruCache<>(16);
private static final ExecutorService DEFAULT_EXECUTOR_SERVICE = RequestExecutor
diff --git a/core/core/src/main/java/androidx/core/view/WindowCompat.java b/core/core/src/main/java/androidx/core/view/WindowCompat.java
index 679f4cf..3c16649 100644
--- a/core/core/src/main/java/androidx/core/view/WindowCompat.java
+++ b/core/core/src/main/java/androidx/core/view/WindowCompat.java
@@ -164,9 +164,13 @@
static void setDecorFitsSystemWindows(@NonNull Window window,
final boolean decorFitsSystemWindows) {
+ final int stableFlag = View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+
final View decorView = window.getDecorView();
final int sysUiVis = decorView.getSystemUiVisibility();
- decorView.setSystemUiVisibility(sysUiVis | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ decorView.setSystemUiVisibility(decorFitsSystemWindows
+ ? sysUiVis & ~stableFlag
+ : sysUiVis | stableFlag);
window.setDecorFitsSystemWindows(decorFitsSystemWindows);
}
}
diff --git a/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java b/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
index f7400fa..e74ec94 100644
--- a/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
+++ b/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
@@ -393,6 +393,8 @@
}
private static class Impl {
+ static final int KEY_BEHAVIOR = 356039078;
+
Impl() {
//private
}
@@ -412,7 +414,7 @@
}
int getSystemBarsBehavior() {
- return 0;
+ return BEHAVIOR_DEFAULT;
}
public boolean isAppearanceLightStatusBars() {
@@ -533,6 +535,7 @@
@Override
void setSystemBarsBehavior(int behavior) {
+ mWindow.getDecorView().setTag(KEY_BEHAVIOR, behavior);
switch (behavior) {
case BEHAVIOR_DEFAULT:
unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
@@ -551,7 +554,8 @@
@Override
int getSystemBarsBehavior() {
- return 0;
+ final Object behaviorTag = mWindow.getDecorView().getTag(KEY_BEHAVIOR);
+ return behaviorTag != null ? (int) behaviorTag : BEHAVIOR_DEFAULT;
}
@Override
@@ -777,7 +781,28 @@
*/
@Override
void setSystemBarsBehavior(@Behavior int behavior) {
- mInsetsController.setSystemBarsBehavior(behavior);
+ if (mWindow != null) {
+ // Use the legacy way to control the behavior as a workaround because API 30 has a
+ // bug that the behavior might be cleared unexpectedly after setting a LayoutParam
+ // to a window.
+ mWindow.getDecorView().setTag(KEY_BEHAVIOR, behavior);
+ switch (behavior) {
+ case BEHAVIOR_DEFAULT:
+ unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ setSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE);
+ break;
+ case BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE:
+ unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE);
+ setSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ break;
+ case BEHAVIOR_SHOW_BARS_BY_TOUCH:
+ unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ break;
+ }
+ } else {
+ mInsetsController.setSystemBarsBehavior(behavior);
+ }
}
/**
@@ -790,7 +815,12 @@
@Override
@Behavior
int getSystemBarsBehavior() {
- return mInsetsController.getSystemBarsBehavior();
+ if (mWindow != null) {
+ final Object behaviorTag = mWindow.getDecorView().getTag(KEY_BEHAVIOR);
+ return behaviorTag != null ? (int) behaviorTag : BEHAVIOR_DEFAULT;
+ } else {
+ return mInsetsController.getSystemBarsBehavior();
+ }
}
@Override
@@ -839,8 +869,48 @@
}
}
+ @RequiresApi(31)
+ private static class Impl31 extends Impl30 {
+
+ Impl31(@NonNull Window window,
+ @NonNull WindowInsetsControllerCompat compatController,
+ @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) {
+ super(window, compatController, softwareKeyboardControllerCompat);
+ }
+
+ Impl31(@NonNull WindowInsetsController insetsController,
+ @NonNull WindowInsetsControllerCompat compatController,
+ @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) {
+ super(insetsController, compatController, softwareKeyboardControllerCompat);
+ }
+
+ /**
+ * Controls the behavior of system bars.
+ *
+ * @param behavior Determines how the bars behave when being hidden by the application.
+ * @see #getSystemBarsBehavior
+ */
+ @Override
+ void setSystemBarsBehavior(@Behavior int behavior) {
+ mInsetsController.setSystemBarsBehavior(behavior);
+ }
+
+ /**
+ * Retrieves the requested behavior of system bars.
+ *
+ * @return the system bar behavior controlled by this window.
+ * @see #setSystemBarsBehavior(int)
+ */
+ @SuppressLint("WrongConstant")
+ @Override
+ @Behavior
+ int getSystemBarsBehavior() {
+ return mInsetsController.getSystemBarsBehavior();
+ }
+ }
+
@RequiresApi(35)
- private static class Impl35 extends Impl30 {
+ private static class Impl35 extends Impl31 {
Impl35(@NonNull Window window,
@NonNull WindowInsetsControllerCompat compatController,
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java
index 2d72cf9..3f650e5 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java
@@ -21,7 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
-import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import android.content.Context;
@@ -52,7 +52,7 @@
ActivityScenario.launch(TestCredentialsActivity.class);
activityScenario.onActivity(
activity -> {
- assumeFalse(deviceHasGMS(getApplicationContext()));
+ assumeTrue(deviceHasGMS(getApplicationContext()));
com.google.android.gms.auth.api.identity.BeginSignInRequest actualResponse =
androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController.getInstance(activity)
.convertRequestToPlayServices(
@@ -70,7 +70,7 @@
ActivityScenario.launch(TestCredentialsActivity.class);
activityScenario.onActivity(
activity -> {
- assumeFalse(deviceHasGMS(getApplicationContext()));
+ assumeTrue(deviceHasGMS(getApplicationContext()));
com.google.android.gms.auth.api.identity.BeginSignInRequest actualResponse =
androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController.getInstance(activity)
.convertRequestToPlayServices(
@@ -90,7 +90,7 @@
ActivityScenario.launch(TestCredentialsActivity.class);
activityScenario.onActivity(
activity -> {
- assumeFalse(deviceHasGMS(getApplicationContext()));
+ assumeTrue(deviceHasGMS(getApplicationContext()));
assertThrows(
"null get credential request must throw exception",
NullPointerException.class,
@@ -106,7 +106,7 @@
ActivityScenario.launch(TestCredentialsActivity.class);
activityScenario.onActivity(
activity -> {
- assumeFalse(deviceHasGMS(getApplicationContext()));
+ assumeTrue(deviceHasGMS(getApplicationContext()));
assertThrows(
"null sign in credential response must throw exception",
NullPointerException.class,
@@ -133,7 +133,7 @@
activityScenario.onActivity(
activity -> {
- assumeFalse(deviceHasGMS(getApplicationContext()));
+ assumeTrue(deviceHasGMS(getApplicationContext()));
com.google.android.gms.auth.api.identity.BeginSignInRequest actualRequest =
androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController.getInstance(activity)
.convertRequestToPlayServices(
@@ -165,7 +165,7 @@
ActivityScenario.launch(TestCredentialsActivity.class);
activityScenario.onActivity(
activity -> {
- assumeFalse(deviceHasGMS(getApplicationContext()));
+ assumeTrue(deviceHasGMS(getApplicationContext()));
androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController firstInstance =
androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController.getInstance(activity);
androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController secondInstance =
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
index 4bc0d41..53c2296 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
@@ -31,7 +31,7 @@
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import com.google.common.truth.Truth.assertThat
-import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,7 +44,7 @@
fun convertRequestToPlayServices_setPasswordOptionRequestAndFalseAutoSelect_success() {
val activityScenario = ActivityScenario.launch(TestCredentialsActivity::class.java)
activityScenario.onActivity { activity: TestCredentialsActivity? ->
- assumeFalse(deviceHasGMS(getApplicationContext()))
+ assumeTrue(deviceHasGMS(getApplicationContext()))
val actualResponse =
getInstance(activity!!)
.convertRequestToPlayServices(GetCredentialRequest(listOf(GetPasswordOption())))
@@ -57,7 +57,7 @@
fun convertRequestToPlayServices_setPasswordOptionRequestAndTrueAutoSelect_success() {
val activityScenario = ActivityScenario.launch(TestCredentialsActivity::class.java)
activityScenario.onActivity { activity: TestCredentialsActivity? ->
- assumeFalse(deviceHasGMS(getApplicationContext()))
+ assumeTrue(deviceHasGMS(getApplicationContext()))
val actualResponse =
getInstance(activity!!)
.convertRequestToPlayServices(
@@ -83,7 +83,7 @@
.build()
activityScenario.onActivity { activity: TestCredentialsActivity? ->
- assumeFalse(deviceHasGMS(getApplicationContext()))
+ assumeTrue(deviceHasGMS(getApplicationContext()))
val actualRequest =
getInstance(activity!!)
.convertRequestToPlayServices(GetCredentialRequest(listOf(option)))
@@ -106,7 +106,7 @@
fun duplicateGetInstance_shouldBeUnequal() {
val activityScenario = ActivityScenario.launch(TestCredentialsActivity::class.java)
activityScenario.onActivity { activity: TestCredentialsActivity? ->
- assumeFalse(deviceHasGMS(getApplicationContext()))
+ assumeTrue(deviceHasGMS(getApplicationContext()))
val firstInstance = getInstance(activity!!)
val secondInstance = getInstance(activity)
assertThat(firstInstance).isNotEqualTo(secondInstance)
diff --git a/datastore/datastore-core-okio/bcv/native/current.txt b/datastore/datastore-core-okio/bcv/native/current.txt
index a4e9063..283d853 100644
--- a/datastore/datastore-core-okio/bcv/native/current.txt
+++ b/datastore/datastore-core-okio/bcv/native/current.txt
@@ -1,5 +1,5 @@
// Klib ABI Dump
-// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64]
+// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
// Rendering settings:
// - Signature version: 2
// - Show manifest properties: true
diff --git a/datastore/datastore-core-okio/build.gradle b/datastore/datastore-core-okio/build.gradle
index dc55a3d..76f36b5 100644
--- a/datastore/datastore-core-okio/build.gradle
+++ b/datastore/datastore-core-okio/build.gradle
@@ -35,6 +35,8 @@
mac()
linux()
ios()
+ watchos()
+ tvos()
defaultPlatform(PlatformIdentifier.JVM)
diff --git a/datastore/datastore-core/bcv/native/current.txt b/datastore/datastore-core/bcv/native/current.txt
index 76b70d8..cac599e 100644
--- a/datastore/datastore-core/bcv/native/current.txt
+++ b/datastore/datastore-core/bcv/native/current.txt
@@ -1,5 +1,5 @@
// Klib ABI Dump
-// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64]
+// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
// Rendering settings:
// - Signature version: 2
// - Show manifest properties: true
diff --git a/datastore/datastore-core/build.gradle b/datastore/datastore-core/build.gradle
index 0a0a304..f184764 100644
--- a/datastore/datastore-core/build.gradle
+++ b/datastore/datastore-core/build.gradle
@@ -67,6 +67,8 @@
mac()
linux()
ios()
+ watchos()
+ tvos()
android()
defaultPlatform(PlatformIdentifier.ANDROID)
@@ -80,7 +82,7 @@
dependencies {
api(libs.kotlinStdlib)
api(libs.kotlinCoroutinesCore)
- api(project(":annotation:annotation"))
+ api("androidx.annotation:annotation:1.8.1")
}
}
diff --git a/datastore/datastore-preferences-core/bcv/native/current.txt b/datastore/datastore-preferences-core/bcv/native/current.txt
index 3953afe..e0837fe 100644
--- a/datastore/datastore-preferences-core/bcv/native/current.txt
+++ b/datastore/datastore-preferences-core/bcv/native/current.txt
@@ -1,5 +1,5 @@
// Klib ABI Dump
-// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64]
+// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
// Rendering settings:
// - Signature version: 2
// - Show manifest properties: true
diff --git a/datastore/datastore-preferences-core/build.gradle b/datastore/datastore-preferences-core/build.gradle
index 94c61c8..efa4b1f 100644
--- a/datastore/datastore-preferences-core/build.gradle
+++ b/datastore/datastore-preferences-core/build.gradle
@@ -37,6 +37,8 @@
mac()
linux()
ios()
+ watchos()
+ tvos()
// NOTE, if you add android target here, make sure to add the proguard file as well.
defaultPlatform(PlatformIdentifier.JVM)
diff --git a/datastore/datastore-preferences/bcv/native/current.txt b/datastore/datastore-preferences/bcv/native/current.txt
index 68d64eb..6d72e0f 100644
--- a/datastore/datastore-preferences/bcv/native/current.txt
+++ b/datastore/datastore-preferences/bcv/native/current.txt
@@ -1,5 +1,5 @@
// Klib ABI Dump
-// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64]
+// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
// Rendering settings:
// - Signature version: 2
// - Show manifest properties: true
diff --git a/datastore/datastore-preferences/build.gradle b/datastore/datastore-preferences/build.gradle
index 6fcf34e..1bf3287 100644
--- a/datastore/datastore-preferences/build.gradle
+++ b/datastore/datastore-preferences/build.gradle
@@ -37,6 +37,8 @@
mac()
linux()
ios()
+ watchos()
+ tvos()
android()
defaultPlatform(PlatformIdentifier.ANDROID)
diff --git a/datastore/datastore/bcv/native/current.txt b/datastore/datastore/bcv/native/current.txt
index 3c9cae7..3ad1416 100644
--- a/datastore/datastore/bcv/native/current.txt
+++ b/datastore/datastore/bcv/native/current.txt
@@ -1,5 +1,5 @@
// Klib ABI Dump
-// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64]
+// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
// Rendering settings:
// - Signature version: 2
// - Show manifest properties: true
diff --git a/datastore/datastore/build.gradle b/datastore/datastore/build.gradle
index c59b19f..6bdb7be 100644
--- a/datastore/datastore/build.gradle
+++ b/datastore/datastore/build.gradle
@@ -37,6 +37,8 @@
jvm()
mac()
ios()
+ watchos()
+ tvos()
linux()
android()
@@ -47,7 +49,7 @@
dependencies {
api(libs.kotlinStdlib)
api(libs.kotlinCoroutinesCore)
- api(project(":annotation:annotation"))
+ api("androidx.annotation:annotation:1.8.1")
api(project(":datastore:datastore-core"))
api(project(":datastore:datastore-core-okio"))
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index cc94802..ef118d2 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -225,6 +225,8 @@
kotlinXHtml = { module = "org.jetbrains.kotlinx:kotlinx-html-jvm", version = "0.7.3" }
ksp = { module = "com.google.devtools.ksp:symbol-processing", version.ref = "ksp" }
kspApi = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
+kspCommon = { module = "com.google.devtools.ksp:symbol-processing-common-deps", version.ref = "ksp" }
+kspEmbeddable = { module = "com.google.devtools.ksp:symbol-processing-aa-embeddable", version.ref = "ksp" }
kspGradlePluginz = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }
ktfmt = { module = "com.facebook:ktfmt", version.ref = "ktfmt" }
kxml2 = { module = "net.sf.kxml:kxml2", version = "2.3.0" }
diff --git a/hilt/hilt-compiler/build.gradle b/hilt/hilt-compiler/build.gradle
index 7980e5f..3213852 100644
--- a/hilt/hilt-compiler/build.gradle
+++ b/hilt/hilt-compiler/build.gradle
@@ -39,7 +39,7 @@
implementation(libs.kspApi)
testImplementation(project(":hilt:hilt-common"))
- testImplementation(project(":annotation:annotation"))
+ testImplementation("androidx.annotation:annotation:1.8.1")
testImplementation(libs.junit)
testImplementation(libs.truth)
testImplementation(project(":room:room-compiler-processing-testing"))
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt
index 3d39c0e..ee50a22 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt
@@ -42,6 +42,8 @@
return emptySet()
}
+ // usage of findTypeElement and requireType with -Pandroidx.maxDepVersions=true
+ @Suppress("DEPRECATION")
private fun parse(env: XProcessingEnv, workerTypeElement: XTypeElement): WorkerElement? {
var valid = true
diff --git a/kruth/kruth/api/api_lint.ignore b/kruth/kruth/api/api_lint.ignore
index 68e708e..7a03d99 100644
--- a/kruth/kruth/api/api_lint.ignore
+++ b/kruth/kruth/api/api_lint.ignore
@@ -85,12 +85,6 @@
Callback method names must follow the on<Something> style: invokeAssertion
-DocumentExceptions: androidx.kruth.ExpectFailure#getFailure():
- Method ExpectFailure.getFailure appears to be throwing java.lang.AssertionError; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.kruth.ExpectFailure#whenTesting():
- Method ExpectFailure.whenTesting appears to be throwing androidx.kruth.AssertionErrorWithFacts; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-
-
ExecutorRegistration: androidx.kruth.ExpectFailure#expectFailure(androidx.kruth.ExpectFailure.StandardSubjectBuilderCallback):
Registration methods should have overload that accepts delivery Executor: `expectFailure`
ExecutorRegistration: androidx.kruth.ExpectFailure.Companion#expectFailure(androidx.kruth.ExpectFailure.StandardSubjectBuilderCallback):
@@ -98,13 +92,13 @@
InvalidNullabilityOverride: androidx.kruth.Expect#apply(org.junit.runners.model.Statement, org.junit.runner.Description) parameter #0:
- Invalid nullability on parameter `base` in method `apply`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+ Invalid nullability on type org.junit.runners.model.Statement in parameter `base` in method `apply`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is platform-nullness.
InvalidNullabilityOverride: androidx.kruth.Expect#apply(org.junit.runners.model.Statement, org.junit.runner.Description) parameter #1:
- Invalid nullability on parameter `description` in method `apply`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+ Invalid nullability on type org.junit.runner.Description in parameter `description` in method `apply`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is platform-nullness.
InvalidNullabilityOverride: androidx.kruth.ExpectFailure#apply(org.junit.runners.model.Statement, org.junit.runner.Description) parameter #0:
- Invalid nullability on parameter `base` in method `apply`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+ Invalid nullability on type org.junit.runners.model.Statement in parameter `base` in method `apply`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is platform-nullness.
InvalidNullabilityOverride: androidx.kruth.ExpectFailure#apply(org.junit.runners.model.Statement, org.junit.runner.Description) parameter #1:
- Invalid nullability on parameter `description` in method `apply`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+ Invalid nullability on type org.junit.runner.Description in parameter `description` in method `apply`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is platform-nullness.
MissingBuildMethod: androidx.kruth.SimpleSubjectBuilder:
diff --git a/kruth/kruth/api/current.ignore b/kruth/kruth/api/current.ignore
index c12fb51..b21650b 100644
--- a/kruth/kruth/api/current.ignore
+++ b/kruth/kruth/api/current.ignore
@@ -25,6 +25,14 @@
Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.IterableSubject to androidx.kruth.IterableSubject<T>
ChangedType: androidx.kruth.StandardSubjectBuilder#that(T):
Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.ComparableSubject<ComparableT> to androidx.kruth.ComparableSubject<T>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.base.Optional<T>):
+ Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.GuavaOptionalSubject to androidx.kruth.GuavaOptionalSubject<T>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multimap<K,V>):
+ Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.MultimapSubject to androidx.kruth.MultimapSubject<K,V>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multiset<T>):
+ Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.MultisetSubject to androidx.kruth.MultisetSubject<T>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Table<R,C,V>):
+ Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.TableSubject to androidx.kruth.TableSubject<R,C,V>
ChangedType: androidx.kruth.StandardSubjectBuilder#that(java.util.Map<K,? extends V>):
Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.MapSubject to androidx.kruth.MapSubject<K,V>
ChangedType: androidx.kruth.Subject.Factory#createSubject(androidx.kruth.FailureMetadata, ActualT):
@@ -39,24 +47,36 @@
InvalidNullConversion: androidx.kruth.IterableSubject#isNotIn(Iterable<?>) parameter #0:
Attempted to change nullability of java.lang.Object (from NONNULL to NULLABLE) in parameter iterable in androidx.kruth.IterableSubject.isNotIn(Iterable<?> iterable)
+InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#that(Class<?>) parameter #0:
+ Attempted to change nullability of java.lang.Class<?> (from NULLABLE to NONNULL) in parameter actual in androidx.kruth.StandardSubjectBuilder.that(Class<?> actual)
InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#that(T[]) parameter #0:
Attempted to change nullability of T (from NULLABLE to NONNULL) in parameter actual in androidx.kruth.StandardSubjectBuilder.that(T[] actual)
+InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#that(java.math.BigDecimal) parameter #0:
+ Attempted to change nullability of java.math.BigDecimal (from NULLABLE to NONNULL) in parameter actual in androidx.kruth.StandardSubjectBuilder.that(java.math.BigDecimal actual)
InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#withMessage(String) parameter #0:
Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter messageToPrepend in androidx.kruth.StandardSubjectBuilder.withMessage(String messageToPrepend)
InvalidNullConversion: androidx.kruth.StringSubject#contains(CharSequence) parameter #0:
Attempted to change nullability of java.lang.CharSequence (from NULLABLE to NONNULL) in parameter charSequence in androidx.kruth.StringSubject.contains(CharSequence charSequence)
InvalidNullConversion: androidx.kruth.StringSubject#containsMatch(String) parameter #0:
Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.containsMatch(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#containsMatch(java.util.regex.Pattern) parameter #0:
+ Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.containsMatch(java.util.regex.Pattern regex)
InvalidNullConversion: androidx.kruth.StringSubject#doesNotContain(CharSequence) parameter #0:
Attempted to change nullability of java.lang.CharSequence (from NULLABLE to NONNULL) in parameter charSequence in androidx.kruth.StringSubject.doesNotContain(CharSequence charSequence)
InvalidNullConversion: androidx.kruth.StringSubject#doesNotContainMatch(String) parameter #0:
Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotContainMatch(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#doesNotContainMatch(java.util.regex.Pattern) parameter #0:
+ Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotContainMatch(java.util.regex.Pattern regex)
InvalidNullConversion: androidx.kruth.StringSubject#doesNotMatch(String) parameter #0:
Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotMatch(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#doesNotMatch(java.util.regex.Pattern) parameter #0:
+ Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotMatch(java.util.regex.Pattern regex)
InvalidNullConversion: androidx.kruth.StringSubject#endsWith(String) parameter #0:
Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter string in androidx.kruth.StringSubject.endsWith(String string)
InvalidNullConversion: androidx.kruth.StringSubject#matches(String) parameter #0:
Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.matches(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#matches(java.util.regex.Pattern) parameter #0:
+ Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.matches(java.util.regex.Pattern regex)
InvalidNullConversion: androidx.kruth.StringSubject#startsWith(String) parameter #0:
Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter string in androidx.kruth.StringSubject.startsWith(String string)
InvalidNullConversion: androidx.kruth.StringSubject.CaseInsensitiveStringComparison#doesNotContain(CharSequence) parameter #0:
@@ -93,10 +113,6 @@
Class androidx.kruth.Fact no longer implements java.io.Serializable
-RemovedMethod: androidx.kruth.ComparableSubject#isIn(com.google.common.collect.Range<T>):
- Removed method androidx.kruth.ComparableSubject.isIn(com.google.common.collect.Range<T>)
-RemovedMethod: androidx.kruth.ComparableSubject#isNotIn(com.google.common.collect.Range<T>):
- Removed method androidx.kruth.ComparableSubject.isNotIn(com.google.common.collect.Range<T>)
RemovedMethod: androidx.kruth.IterableSubject#IterableSubject(androidx.kruth.FailureMetadata, Iterable<?>):
Removed constructor androidx.kruth.IterableSubject(androidx.kruth.FailureMetadata,Iterable<?>)
RemovedMethod: androidx.kruth.IterableSubject#comparingElementsUsing(androidx.kruth.Correspondence<? super A,? super E>):
@@ -143,28 +159,8 @@
Removed method androidx.kruth.PrimitiveFloatArraySubject.usingTolerance(double)
RemovedMethod: androidx.kruth.StandardSubjectBuilder#about(androidx.kruth.CustomSubjectBuilder.Factory<CustomSubjectBuilderT>):
Removed method androidx.kruth.StandardSubjectBuilder.about(androidx.kruth.CustomSubjectBuilder.Factory<CustomSubjectBuilderT>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(Class<?>):
- Removed method androidx.kruth.StandardSubjectBuilder.that(Class<?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.base.Optional<?>):
- Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.base.Optional<?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multimap<?,?>):
- Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.collect.Multimap<?,?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multiset<?>):
- Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.collect.Multiset<?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Table<?,?,?>):
- Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.collect.Table<?,?,?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(java.math.BigDecimal):
- Removed method androidx.kruth.StandardSubjectBuilder.that(java.math.BigDecimal)
RemovedMethod: androidx.kruth.StandardSubjectBuilder#withMessage(String, java.lang.Object...):
Removed method androidx.kruth.StandardSubjectBuilder.withMessage(String,java.lang.Object...)
-RemovedMethod: androidx.kruth.StringSubject#containsMatch(java.util.regex.Pattern):
- Removed method androidx.kruth.StringSubject.containsMatch(java.util.regex.Pattern)
-RemovedMethod: androidx.kruth.StringSubject#doesNotContainMatch(java.util.regex.Pattern):
- Removed method androidx.kruth.StringSubject.doesNotContainMatch(java.util.regex.Pattern)
-RemovedMethod: androidx.kruth.StringSubject#doesNotMatch(java.util.regex.Pattern):
- Removed method androidx.kruth.StringSubject.doesNotMatch(java.util.regex.Pattern)
-RemovedMethod: androidx.kruth.StringSubject#matches(java.util.regex.Pattern):
- Removed method androidx.kruth.StringSubject.matches(java.util.regex.Pattern)
RemovedMethod: androidx.kruth.Subject#Subject(androidx.kruth.FailureMetadata, Object):
Removed constructor androidx.kruth.Subject(androidx.kruth.FailureMetadata,Object)
RemovedMethod: androidx.kruth.Subject#isInstanceOf(Class<?>):
diff --git a/kruth/kruth/api/current.txt b/kruth/kruth/api/current.txt
index 717aa87..889c6aa 100644
--- a/kruth/kruth/api/current.txt
+++ b/kruth/kruth/api/current.txt
@@ -23,7 +23,9 @@
method public final void isAtMost(T? other);
method public void isEquivalentAccordingToCompareTo(T? other);
method public final void isGreaterThan(T? other);
+ method public final void isIn(com.google.common.collect.Range<T> range);
method public final void isLessThan(T? other);
+ method public final void isNotIn(com.google.common.collect.Range<T> range);
}
public final class DoubleSubject extends androidx.kruth.ComparableSubject<java.lang.Double> {
@@ -334,15 +336,21 @@
method public final androidx.kruth.PrimitiveByteArraySubject that(byte[]? actual);
method public final androidx.kruth.Subject<java.lang.Character> that(char actual);
method public final androidx.kruth.PrimitiveCharArraySubject that(char[]? actual);
+ method public final <T> androidx.kruth.GuavaOptionalSubject<T> that(com.google.common.base.Optional<T> actual);
+ method public final <K, V> androidx.kruth.MultimapSubject<K,V> that(com.google.common.collect.Multimap<K,V> actual);
+ method public final <T> androidx.kruth.MultisetSubject<T> that(com.google.common.collect.Multiset<T> actual);
+ method public final <R, C, V> androidx.kruth.TableSubject<R,C,V> that(com.google.common.collect.Table<R,C,V> actual);
method public final androidx.kruth.PrimitiveDoubleArraySubject that(double[]? actual);
method public final androidx.kruth.PrimitiveFloatArraySubject that(float[]? actual);
method public final androidx.kruth.IntegerSubject that(int actual);
method public final androidx.kruth.PrimitiveIntArraySubject that(int[]? actual);
method public final androidx.kruth.BooleanSubject that(Boolean? actual);
+ method public final androidx.kruth.ClassSubject that(Class<? extends java.lang.Object?> actual);
method public final androidx.kruth.DoubleSubject that(Double? actual);
method public final androidx.kruth.FloatSubject that(Float? actual);
method public final <T> androidx.kruth.IterableSubject<T> that(Iterable<? extends T>? actual);
method public final androidx.kruth.StringSubject that(String? actual);
+ method public final androidx.kruth.BigDecimalSubject that(java.math.BigDecimal actual);
method public final <K, V> androidx.kruth.MapSubject<K,V> that(java.util.Map<K,? extends V>? actual);
method public final androidx.kruth.LongSubject that(long actual);
method public final androidx.kruth.PrimitiveLongArraySubject that(long[]? actual);
@@ -365,11 +373,14 @@
ctor protected StringSubject(androidx.kruth.FailureMetadata metadata, String? actual);
method public void contains(CharSequence charSequence);
method public void containsMatch(String regex);
+ method public void containsMatch(java.util.regex.Pattern regex);
method public final void containsMatch(kotlin.text.Regex regex);
method public void doesNotContain(CharSequence charSequence);
method public void doesNotContainMatch(String regex);
+ method public void doesNotContainMatch(java.util.regex.Pattern regex);
method public final void doesNotContainMatch(kotlin.text.Regex regex);
method public void doesNotMatch(String regex);
+ method public void doesNotMatch(java.util.regex.Pattern regex);
method public final void doesNotMatch(kotlin.text.Regex regex);
method public void endsWith(String string);
method public void hasLength(int expectedLength);
@@ -377,6 +388,7 @@
method public void isEmpty();
method public void isNotEmpty();
method public void matches(String regex);
+ method public void matches(java.util.regex.Pattern regex);
method public final void matches(kotlin.text.Regex regex);
method public void startsWith(String string);
}
diff --git a/kruth/kruth/api/restricted_current.ignore b/kruth/kruth/api/restricted_current.ignore
index c12fb51..b21650b 100644
--- a/kruth/kruth/api/restricted_current.ignore
+++ b/kruth/kruth/api/restricted_current.ignore
@@ -25,6 +25,14 @@
Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.IterableSubject to androidx.kruth.IterableSubject<T>
ChangedType: androidx.kruth.StandardSubjectBuilder#that(T):
Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.ComparableSubject<ComparableT> to androidx.kruth.ComparableSubject<T>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.base.Optional<T>):
+ Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.GuavaOptionalSubject to androidx.kruth.GuavaOptionalSubject<T>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multimap<K,V>):
+ Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.MultimapSubject to androidx.kruth.MultimapSubject<K,V>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multiset<T>):
+ Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.MultisetSubject to androidx.kruth.MultisetSubject<T>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Table<R,C,V>):
+ Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.TableSubject to androidx.kruth.TableSubject<R,C,V>
ChangedType: androidx.kruth.StandardSubjectBuilder#that(java.util.Map<K,? extends V>):
Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.MapSubject to androidx.kruth.MapSubject<K,V>
ChangedType: androidx.kruth.Subject.Factory#createSubject(androidx.kruth.FailureMetadata, ActualT):
@@ -39,24 +47,36 @@
InvalidNullConversion: androidx.kruth.IterableSubject#isNotIn(Iterable<?>) parameter #0:
Attempted to change nullability of java.lang.Object (from NONNULL to NULLABLE) in parameter iterable in androidx.kruth.IterableSubject.isNotIn(Iterable<?> iterable)
+InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#that(Class<?>) parameter #0:
+ Attempted to change nullability of java.lang.Class<?> (from NULLABLE to NONNULL) in parameter actual in androidx.kruth.StandardSubjectBuilder.that(Class<?> actual)
InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#that(T[]) parameter #0:
Attempted to change nullability of T (from NULLABLE to NONNULL) in parameter actual in androidx.kruth.StandardSubjectBuilder.that(T[] actual)
+InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#that(java.math.BigDecimal) parameter #0:
+ Attempted to change nullability of java.math.BigDecimal (from NULLABLE to NONNULL) in parameter actual in androidx.kruth.StandardSubjectBuilder.that(java.math.BigDecimal actual)
InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#withMessage(String) parameter #0:
Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter messageToPrepend in androidx.kruth.StandardSubjectBuilder.withMessage(String messageToPrepend)
InvalidNullConversion: androidx.kruth.StringSubject#contains(CharSequence) parameter #0:
Attempted to change nullability of java.lang.CharSequence (from NULLABLE to NONNULL) in parameter charSequence in androidx.kruth.StringSubject.contains(CharSequence charSequence)
InvalidNullConversion: androidx.kruth.StringSubject#containsMatch(String) parameter #0:
Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.containsMatch(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#containsMatch(java.util.regex.Pattern) parameter #0:
+ Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.containsMatch(java.util.regex.Pattern regex)
InvalidNullConversion: androidx.kruth.StringSubject#doesNotContain(CharSequence) parameter #0:
Attempted to change nullability of java.lang.CharSequence (from NULLABLE to NONNULL) in parameter charSequence in androidx.kruth.StringSubject.doesNotContain(CharSequence charSequence)
InvalidNullConversion: androidx.kruth.StringSubject#doesNotContainMatch(String) parameter #0:
Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotContainMatch(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#doesNotContainMatch(java.util.regex.Pattern) parameter #0:
+ Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotContainMatch(java.util.regex.Pattern regex)
InvalidNullConversion: androidx.kruth.StringSubject#doesNotMatch(String) parameter #0:
Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotMatch(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#doesNotMatch(java.util.regex.Pattern) parameter #0:
+ Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotMatch(java.util.regex.Pattern regex)
InvalidNullConversion: androidx.kruth.StringSubject#endsWith(String) parameter #0:
Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter string in androidx.kruth.StringSubject.endsWith(String string)
InvalidNullConversion: androidx.kruth.StringSubject#matches(String) parameter #0:
Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.matches(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#matches(java.util.regex.Pattern) parameter #0:
+ Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.matches(java.util.regex.Pattern regex)
InvalidNullConversion: androidx.kruth.StringSubject#startsWith(String) parameter #0:
Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter string in androidx.kruth.StringSubject.startsWith(String string)
InvalidNullConversion: androidx.kruth.StringSubject.CaseInsensitiveStringComparison#doesNotContain(CharSequence) parameter #0:
@@ -93,10 +113,6 @@
Class androidx.kruth.Fact no longer implements java.io.Serializable
-RemovedMethod: androidx.kruth.ComparableSubject#isIn(com.google.common.collect.Range<T>):
- Removed method androidx.kruth.ComparableSubject.isIn(com.google.common.collect.Range<T>)
-RemovedMethod: androidx.kruth.ComparableSubject#isNotIn(com.google.common.collect.Range<T>):
- Removed method androidx.kruth.ComparableSubject.isNotIn(com.google.common.collect.Range<T>)
RemovedMethod: androidx.kruth.IterableSubject#IterableSubject(androidx.kruth.FailureMetadata, Iterable<?>):
Removed constructor androidx.kruth.IterableSubject(androidx.kruth.FailureMetadata,Iterable<?>)
RemovedMethod: androidx.kruth.IterableSubject#comparingElementsUsing(androidx.kruth.Correspondence<? super A,? super E>):
@@ -143,28 +159,8 @@
Removed method androidx.kruth.PrimitiveFloatArraySubject.usingTolerance(double)
RemovedMethod: androidx.kruth.StandardSubjectBuilder#about(androidx.kruth.CustomSubjectBuilder.Factory<CustomSubjectBuilderT>):
Removed method androidx.kruth.StandardSubjectBuilder.about(androidx.kruth.CustomSubjectBuilder.Factory<CustomSubjectBuilderT>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(Class<?>):
- Removed method androidx.kruth.StandardSubjectBuilder.that(Class<?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.base.Optional<?>):
- Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.base.Optional<?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multimap<?,?>):
- Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.collect.Multimap<?,?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multiset<?>):
- Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.collect.Multiset<?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Table<?,?,?>):
- Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.collect.Table<?,?,?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(java.math.BigDecimal):
- Removed method androidx.kruth.StandardSubjectBuilder.that(java.math.BigDecimal)
RemovedMethod: androidx.kruth.StandardSubjectBuilder#withMessage(String, java.lang.Object...):
Removed method androidx.kruth.StandardSubjectBuilder.withMessage(String,java.lang.Object...)
-RemovedMethod: androidx.kruth.StringSubject#containsMatch(java.util.regex.Pattern):
- Removed method androidx.kruth.StringSubject.containsMatch(java.util.regex.Pattern)
-RemovedMethod: androidx.kruth.StringSubject#doesNotContainMatch(java.util.regex.Pattern):
- Removed method androidx.kruth.StringSubject.doesNotContainMatch(java.util.regex.Pattern)
-RemovedMethod: androidx.kruth.StringSubject#doesNotMatch(java.util.regex.Pattern):
- Removed method androidx.kruth.StringSubject.doesNotMatch(java.util.regex.Pattern)
-RemovedMethod: androidx.kruth.StringSubject#matches(java.util.regex.Pattern):
- Removed method androidx.kruth.StringSubject.matches(java.util.regex.Pattern)
RemovedMethod: androidx.kruth.Subject#Subject(androidx.kruth.FailureMetadata, Object):
Removed constructor androidx.kruth.Subject(androidx.kruth.FailureMetadata,Object)
RemovedMethod: androidx.kruth.Subject#isInstanceOf(Class<?>):
diff --git a/kruth/kruth/api/restricted_current.txt b/kruth/kruth/api/restricted_current.txt
index 204fee0..78e2d5a 100644
--- a/kruth/kruth/api/restricted_current.txt
+++ b/kruth/kruth/api/restricted_current.txt
@@ -23,7 +23,9 @@
method public final void isAtMost(T? other);
method public void isEquivalentAccordingToCompareTo(T? other);
method public final void isGreaterThan(T? other);
+ method public final void isIn(com.google.common.collect.Range<T> range);
method public final void isLessThan(T? other);
+ method public final void isNotIn(com.google.common.collect.Range<T> range);
}
public final class DoubleSubject extends androidx.kruth.ComparableSubject<java.lang.Double> {
@@ -334,15 +336,21 @@
method public final androidx.kruth.PrimitiveByteArraySubject that(byte[]? actual);
method public final androidx.kruth.Subject<java.lang.Character> that(char actual);
method public final androidx.kruth.PrimitiveCharArraySubject that(char[]? actual);
+ method public final <T> androidx.kruth.GuavaOptionalSubject<T> that(com.google.common.base.Optional<T> actual);
+ method public final <K, V> androidx.kruth.MultimapSubject<K,V> that(com.google.common.collect.Multimap<K,V> actual);
+ method public final <T> androidx.kruth.MultisetSubject<T> that(com.google.common.collect.Multiset<T> actual);
+ method public final <R, C, V> androidx.kruth.TableSubject<R,C,V> that(com.google.common.collect.Table<R,C,V> actual);
method public final androidx.kruth.PrimitiveDoubleArraySubject that(double[]? actual);
method public final androidx.kruth.PrimitiveFloatArraySubject that(float[]? actual);
method public final androidx.kruth.IntegerSubject that(int actual);
method public final androidx.kruth.PrimitiveIntArraySubject that(int[]? actual);
method public final androidx.kruth.BooleanSubject that(Boolean? actual);
+ method public final androidx.kruth.ClassSubject that(Class<? extends java.lang.Object?> actual);
method public final androidx.kruth.DoubleSubject that(Double? actual);
method public final androidx.kruth.FloatSubject that(Float? actual);
method public final <T> androidx.kruth.IterableSubject<T> that(Iterable<? extends T>? actual);
method public final androidx.kruth.StringSubject that(String? actual);
+ method public final androidx.kruth.BigDecimalSubject that(java.math.BigDecimal actual);
method public final <K, V> androidx.kruth.MapSubject<K,V> that(java.util.Map<K,? extends V>? actual);
method public final androidx.kruth.LongSubject that(long actual);
method public final androidx.kruth.PrimitiveLongArraySubject that(long[]? actual);
@@ -365,11 +373,14 @@
ctor protected StringSubject(androidx.kruth.FailureMetadata metadata, String? actual);
method public void contains(CharSequence charSequence);
method public void containsMatch(String regex);
+ method public void containsMatch(java.util.regex.Pattern regex);
method public final void containsMatch(kotlin.text.Regex regex);
method public void doesNotContain(CharSequence charSequence);
method public void doesNotContainMatch(String regex);
+ method public void doesNotContainMatch(java.util.regex.Pattern regex);
method public final void doesNotContainMatch(kotlin.text.Regex regex);
method public void doesNotMatch(String regex);
+ method public void doesNotMatch(java.util.regex.Pattern regex);
method public final void doesNotMatch(kotlin.text.Regex regex);
method public void endsWith(String string);
method public void hasLength(int expectedLength);
@@ -377,6 +388,7 @@
method public void isEmpty();
method public void isNotEmpty();
method public void matches(String regex);
+ method public void matches(java.util.regex.Pattern regex);
method public final void matches(kotlin.text.Regex regex);
method public void startsWith(String string);
}
diff --git a/kruth/kruth/bcv/native/current.txt b/kruth/kruth/bcv/native/current.txt
index d555412..fd89b84 100644
--- a/kruth/kruth/bcv/native/current.txt
+++ b/kruth/kruth/bcv/native/current.txt
@@ -207,7 +207,7 @@
open fun isNoneOf(kotlin/Any?, kotlin/Any?, kotlin/Array<out kotlin/Any?>...) // androidx.kruth/IterableSubject.isNoneOf|isNoneOf(kotlin.Any?;kotlin.Any?;kotlin.Array<out|kotlin.Any?>...){}[0]
open fun isNotIn(kotlin.collections/Iterable<*>?) // androidx.kruth/IterableSubject.isNotIn|isNotIn(kotlin.collections.Iterable<*>?){}[0]
}
-open class <#A: kotlin/Comparable<#A>> androidx.kruth/ComparableSubject : androidx.kruth/PlatformComparableSubject<#A>, androidx.kruth/Subject<#A> { // androidx.kruth/ComparableSubject|null[0]
+open class <#A: kotlin/Comparable<#A>> androidx.kruth/ComparableSubject : androidx.kruth/Subject<#A> { // androidx.kruth/ComparableSubject|null[0]
constructor <init>(androidx.kruth/FailureMetadata, #A?) // androidx.kruth/ComparableSubject.<init>|<init>(androidx.kruth.FailureMetadata;1:0?){}[0]
final fun isAtLeast(#A?) // androidx.kruth/ComparableSubject.isAtLeast|isAtLeast(1:0?){}[0]
final fun isAtMost(#A?) // androidx.kruth/ComparableSubject.isAtMost|isAtMost(1:0?){}[0]
@@ -264,7 +264,7 @@
final fun isWithin(kotlin/Long): androidx.kruth/LongSubject.TolerantLongComparison // androidx.kruth/LongSubject.isWithin|isWithin(kotlin.Long){}[0]
open fun isEquivalentAccordingToCompareTo(kotlin/Long?) // androidx.kruth/LongSubject.isEquivalentAccordingToCompareTo|isEquivalentAccordingToCompareTo(kotlin.Long?){}[0]
}
-open class androidx.kruth/StandardSubjectBuilder : androidx.kruth/PlatformStandardSubjectBuilder { // androidx.kruth/StandardSubjectBuilder|null[0]
+open class androidx.kruth/StandardSubjectBuilder { // androidx.kruth/StandardSubjectBuilder|null[0]
final fun <#A1: kotlin/Any?, #B1: androidx.kruth/Subject<#A1>> about(androidx.kruth/Subject.Factory<#B1, #A1>): androidx.kruth/SimpleSubjectBuilder<#B1, #A1> // androidx.kruth/StandardSubjectBuilder.about|about(androidx.kruth.Subject.Factory<0:1,0:0>){0§<kotlin.Any?>;1§<androidx.kruth.Subject<0:0>>}[0]
final fun <#A1: kotlin/Any?, #B1: kotlin/Any?> that(kotlin.collections/Map<#A1, #B1>?): androidx.kruth/MapSubject<#A1, #B1> // androidx.kruth/StandardSubjectBuilder.that|that(kotlin.collections.Map<0:0,0:1>?){0§<kotlin.Any?>;1§<kotlin.Any?>}[0]
final fun <#A1: kotlin/Any?> that(#A1?): androidx.kruth/Subject<#A1> // androidx.kruth/StandardSubjectBuilder.that|that(0:0?){0§<kotlin.Any?>}[0]
@@ -295,7 +295,7 @@
final fun forCustomFailureStrategy(androidx.kruth/FailureStrategy): androidx.kruth/StandardSubjectBuilder // androidx.kruth/StandardSubjectBuilder.Companion.forCustomFailureStrategy|forCustomFailureStrategy(androidx.kruth.FailureStrategy){}[0]
}
}
-open class androidx.kruth/StringSubject : androidx.kruth/ComparableSubject<kotlin/String>, androidx.kruth/PlatformStringSubject { // androidx.kruth/StringSubject|null[0]
+open class androidx.kruth/StringSubject : androidx.kruth/ComparableSubject<kotlin/String> { // androidx.kruth/StringSubject|null[0]
constructor <init>(androidx.kruth/FailureMetadata, kotlin/String?) // androidx.kruth/StringSubject.<init>|<init>(androidx.kruth.FailureMetadata;kotlin.String?){}[0]
final fun containsMatch(kotlin.text/Regex) // androidx.kruth/StringSubject.containsMatch|containsMatch(kotlin.text.Regex){}[0]
final fun doesNotContainMatch(kotlin.text/Regex) // androidx.kruth/StringSubject.doesNotContainMatch|doesNotContainMatch(kotlin.text.Regex){}[0]
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ComparableSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ComparableSubject.kt
index 6c4407b..c13fdd6 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ComparableSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ComparableSubject.kt
@@ -25,15 +25,13 @@
* @constructor Constructor for use by subclasses. If you want to create an instance of this class
* itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
*/
-open class ComparableSubject<T : Comparable<T>>
+expect open class ComparableSubject<T : Comparable<T>>
protected constructor(
metadata: FailureMetadata,
actual: T?,
-) :
- Subject<T>(actual, metadata, typeDescriptionOverride = null),
- PlatformComparableSubject<T> by PlatformComparableSubjectImpl(actual, metadata) {
+) : Subject<T> {
- internal constructor(actual: T?, metadata: FailureMetadata) : this(metadata, actual)
+ internal constructor(actual: T?, metadata: FailureMetadata)
/**
* Checks that the subject is equivalent to [other] according to [Comparable.compareTo], (i.e.,
@@ -41,78 +39,78 @@
*
* **Note:** Do not use this method for checking object equality. Instead, use [isEqualTo].
*/
- open fun isEquivalentAccordingToCompareTo(other: T?) {
- requireNonNull(actual)
- requireNonNull(other)
-
- if (actual.compareTo(other) != 0) {
- failWithActualInternal(fact("Expected value that sorts equal to", other))
- }
- }
+ open fun isEquivalentAccordingToCompareTo(other: T?)
/**
* Checks that the subject is greater than [other].
*
* To check that the subject is greater than *or equal to* [other], use [isAtLeast].
*/
- fun isGreaterThan(other: T?) {
- requireNonNull(actual)
- requireNonNull(other)
-
- if (actual <= other) {
- failWithActualInternal(fact("Expected to be greater than", other))
- }
- }
+ fun isGreaterThan(other: T?)
/**
* Checks that the subject is less than [other].
*
* @throws NullPointerException if [actual] or [other] is `null`.
*/
- fun isLessThan(other: T?) {
- requireNonNull(actual) { "Expected to be less than $other, but was $actual" }
- requireNonNull(other) { "Expected to be less than $other, but was $actual" }
-
- if (actual >= other) {
- failWithActualInternal(fact("Expected to be less than", other))
- }
- }
+ fun isLessThan(other: T?)
/**
* Checks that the subject is less than or equal to [other].
*
* @throws NullPointerException if [actual] or [other] is `null`.
*/
- fun isAtMost(other: T?) {
- requireNonNull(actual) { "Expected to be at most $other, but was $actual" }
- requireNonNull(other) { "Expected to be at most $other, but was $actual" }
- if (actual > other) {
- failWithActualInternal(fact("Expected to be at most", other))
- }
- }
+ fun isAtMost(other: T?)
/**
* Checks that the subject is greater than or equal to [other].
*
* @throws NullPointerException if [actual] or [other] is `null`.
*/
- fun isAtLeast(other: T?) {
- requireNonNull(actual) { "Expected to be at least $other, but was $actual" }
- requireNonNull(other) { "Expected to be at least $other, but was $actual" }
- if (actual < other) {
- failWithActualInternal(fact("Expected to be at least", other))
- }
+ fun isAtLeast(other: T?)
+}
+
+internal fun <T : Comparable<T>> ComparableSubject<T>.commonIsEquivalentAccordingToCompareTo(
+ other: T?
+) {
+ requireNonNull(actual)
+ requireNonNull(other)
+
+ if (actual.compareTo(other) != 0) {
+ failWithActualInternal(fact("Expected value that sorts equal to", other))
}
}
-/**
- * Platform-specific propositions for [Comparable] typed subjects.
- *
- * @param T the type of the object being tested by this [ComparableSubject]
- */
-internal expect interface PlatformComparableSubject<T : Comparable<T>>
+internal fun <T : Comparable<T>> ComparableSubject<T>.commonIsGreaterThan(other: T?) {
+ requireNonNull(actual)
+ requireNonNull(other)
-internal expect class PlatformComparableSubjectImpl<T : Comparable<T>>(
- actual: T?,
- metadata: FailureMetadata,
-) : Subject<T>, PlatformComparableSubject<T>
+ if (actual <= other) {
+ failWithActualInternal(fact("Expected to be greater than", other))
+ }
+}
+
+internal fun <T : Comparable<T>> ComparableSubject<T>.commonIsLessThan(other: T?) {
+ requireNonNull(actual) { "Expected to be less than $other, but was $actual" }
+ requireNonNull(other) { "Expected to be less than $other, but was $actual" }
+
+ if (actual >= other) {
+ failWithActualInternal(fact("Expected to be less than", other))
+ }
+}
+
+internal fun <T : Comparable<T>> ComparableSubject<T>.commonIsAtMost(other: T?) {
+ requireNonNull(actual) { "Expected to be at most $other, but was $actual" }
+ requireNonNull(other) { "Expected to be at most $other, but was $actual" }
+ if (actual > other) {
+ failWithActualInternal(fact("Expected to be at most", other))
+ }
+}
+
+internal fun <T : Comparable<T>> ComparableSubject<T>.commonIsAtLeast(other: T?) {
+ requireNonNull(actual) { "Expected to be at least $other, but was $actual" }
+ requireNonNull(other) { "Expected to be at least $other, but was $actual" }
+ if (actual < other) {
+ failWithActualInternal(fact("Expected to be at least", other))
+ }
+}
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt
index 1b23c84..8348fb9 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt
@@ -18,8 +18,6 @@
import kotlin.jvm.JvmStatic
-typealias CharSubject = Subject<Char>
-
/**
* In a fluent assertion chain, an object with which you can do any of the following:
* - Set an optional message with [withMessage].
@@ -27,22 +25,13 @@
* [withMessage].
*/
@Suppress("StaticFinalBuilder") // Cannot be final for binary compatibility.
-open class StandardSubjectBuilder
-internal constructor(
- metadata: FailureMetadata,
-) : PlatformStandardSubjectBuilder by PlatformStandardSubjectBuilderImpl(metadata) {
- internal val metadata = metadata
- get() {
- checkStatePreconditions()
- return field
- }
+expect open class StandardSubjectBuilder internal constructor(metadata: FailureMetadata) {
+ internal val metadata: FailureMetadata
companion object {
/** Returns a new instance that invokes the given [FailureStrategy] when a check fails. */
@JvmStatic
- fun forCustomFailureStrategy(failureStrategy: FailureStrategy): StandardSubjectBuilder {
- return StandardSubjectBuilder(FailureMetadata(failureStrategy = failureStrategy))
- }
+ fun forCustomFailureStrategy(failureStrategy: FailureStrategy): StandardSubjectBuilder
}
/**
@@ -50,75 +39,59 @@
* this method is called multiple times, the messages will appear in the order that they were
* specified.
*/
- fun withMessage(messageToPrepend: String): StandardSubjectBuilder =
- StandardSubjectBuilder(metadata = metadata.withMessage(message = messageToPrepend))
+ fun withMessage(messageToPrepend: String): StandardSubjectBuilder
- fun <T> that(actual: T?): Subject<T> = Subject(actual = actual, metadata = metadata, null)
+ fun <T> that(actual: T?): Subject<T>
// actual cannot be made nullable due to autoboxing and this overload is necessary to allow
// StandardSubjectBuilder.that(char) from Java to resolve properly as an Object
// (otherwise it is source-incompatibly interpreted as Int).
// See: NumericComparisonTest#testNumericPrimitiveTypes_isNotEqual_shouldFail_charToInt
- fun that(actual: Char): Subject<Char> = Subject(actual = actual, metadata = metadata, null)
+ fun that(actual: Char): Subject<Char>
- fun <T : Comparable<T>> that(actual: T?): ComparableSubject<T> =
- ComparableSubject(actual = actual, metadata = metadata)
+ fun <T : Comparable<T>> that(actual: T?): ComparableSubject<T>
- fun <T : Throwable> that(actual: T?): ThrowableSubject<T> =
- ThrowableSubject(actual = actual, metadata = metadata, "throwable")
+ fun <T : Throwable> that(actual: T?): ThrowableSubject<T>
- fun that(actual: Boolean?): BooleanSubject =
- BooleanSubject(actual = actual, metadata = metadata)
+ fun that(actual: Boolean?): BooleanSubject
- fun that(actual: Long): LongSubject = LongSubject(actual = actual, metadata = metadata)
+ fun that(actual: Long): LongSubject
// Workaround for https://youtrack.jetbrains.com/issue/KT-645
- fun <T : Long?> that(actual: T): LongSubject = LongSubject(actual = actual, metadata = metadata)
+ fun <T : Long?> that(actual: T): LongSubject
- fun that(actual: Double?): DoubleSubject = DoubleSubject(actual = actual, metadata = metadata)
+ fun that(actual: Double?): DoubleSubject
- fun that(actual: Float?): FloatSubject = FloatSubject(actual = actual, metadata = metadata)
+ fun that(actual: Float?): FloatSubject
- fun that(actual: Int): IntegerSubject = IntegerSubject(actual = actual, metadata = metadata)
+ fun that(actual: Int): IntegerSubject
// Workaround for https://youtrack.jetbrains.com/issue/KT-645
- fun <T : Int?> that(actual: T): IntegerSubject =
- IntegerSubject(actual = actual, metadata = metadata)
+ fun <T : Int?> that(actual: T): IntegerSubject
- fun that(actual: String?): StringSubject = StringSubject(actual = actual, metadata = metadata)
+ fun that(actual: String?): StringSubject
- fun <T> that(actual: Iterable<T>?): IterableSubject<T> =
- IterableSubject(actual = actual, metadata = metadata)
+ fun <T> that(actual: Iterable<T>?): IterableSubject<T>
- fun <T> that(actual: Array<out T>?): ObjectArraySubject<T> =
- ObjectArraySubject(actual = actual, metadata = metadata)
+ fun <T> that(actual: Array<out T>?): ObjectArraySubject<T>
- fun that(actual: BooleanArray?): PrimitiveBooleanArraySubject =
- PrimitiveBooleanArraySubject(actual = actual, metadata = metadata)
+ fun that(actual: BooleanArray?): PrimitiveBooleanArraySubject
- fun that(actual: ShortArray?): PrimitiveShortArraySubject =
- PrimitiveShortArraySubject(actual = actual, metadata = metadata)
+ fun that(actual: ShortArray?): PrimitiveShortArraySubject
- fun that(actual: IntArray?): PrimitiveIntArraySubject =
- PrimitiveIntArraySubject(actual = actual, metadata = metadata)
+ fun that(actual: IntArray?): PrimitiveIntArraySubject
- fun that(actual: LongArray?): PrimitiveLongArraySubject =
- PrimitiveLongArraySubject(actual = actual, metadata = metadata)
+ fun that(actual: LongArray?): PrimitiveLongArraySubject
- fun that(actual: ByteArray?): PrimitiveByteArraySubject =
- PrimitiveByteArraySubject(actual = actual, metadata = metadata)
+ fun that(actual: ByteArray?): PrimitiveByteArraySubject
- fun that(actual: CharArray?): PrimitiveCharArraySubject =
- PrimitiveCharArraySubject(actual = actual, metadata = metadata)
+ fun that(actual: CharArray?): PrimitiveCharArraySubject
- fun that(actual: FloatArray?): PrimitiveFloatArraySubject =
- PrimitiveFloatArraySubject(actual = actual, metadata = metadata)
+ fun that(actual: FloatArray?): PrimitiveFloatArraySubject
- fun that(actual: DoubleArray?): PrimitiveDoubleArraySubject =
- PrimitiveDoubleArraySubject(actual = actual, metadata = metadata)
+ fun that(actual: DoubleArray?): PrimitiveDoubleArraySubject
- fun <K, V> that(actual: Map<K, V>?): MapSubject<K, V> =
- MapSubject(actual = actual, metadata = metadata)
+ fun <K, V> that(actual: Map<K, V>?): MapSubject<K, V>
/**
* Given a factory for some [Subject] class, returns [SimpleSubjectBuilder] whose
@@ -127,8 +100,7 @@
*/
fun <T, S : Subject<T>> about(
subjectFactory: Subject.Factory<S, T>,
- ): SimpleSubjectBuilder<S, T> =
- SimpleSubjectBuilder(metadata = metadata, subjectFactory = subjectFactory)
+ ): SimpleSubjectBuilder<S, T>
/**
* Reports a failure.
@@ -136,16 +108,104 @@
* To set a message, first call [withMessage] (or, more commonly, use the shortcut
* [assertWithMessage].
*/
- fun fail() {
- metadata.fail()
- }
+ fun fail()
- internal open fun checkStatePreconditions() {}
+ internal open fun checkStatePreconditions()
}
-/** Platform-specific additions for [StandardSubjectBuilder]. */
-internal expect interface PlatformStandardSubjectBuilder
+internal fun commonForCustomFailureStrategy(
+ failureStrategy: FailureStrategy
+): StandardSubjectBuilder {
+ return StandardSubjectBuilder(FailureMetadata(failureStrategy = failureStrategy))
+}
-internal expect class PlatformStandardSubjectBuilderImpl(
- metadata: FailureMetadata,
-) : PlatformStandardSubjectBuilder
+internal fun StandardSubjectBuilder.commonWithMessage(
+ messageToPrepend: String
+): StandardSubjectBuilder =
+ StandardSubjectBuilder(metadata = metadata.withMessage(message = messageToPrepend))
+
+internal fun <T> StandardSubjectBuilder.commonThat(actual: T?): Subject<T> =
+ Subject(actual = actual, metadata = metadata, null)
+
+// actual cannot be made nullable due to autoboxing and this overload is necessary to allow
+// StandardSubjectBuilder.that(char) from Java to resolve properly as an Object
+// (otherwise it is source-incompatibly interpreted as Int).
+// See: NumericComparisonTest#testNumericPrimitiveTypes_isNotEqual_shouldFail_charToInt
+internal fun StandardSubjectBuilder.commonThat(actual: Char): Subject<Char> =
+ Subject(actual = actual, metadata = metadata, null)
+
+internal fun <T : Comparable<T>> StandardSubjectBuilder.commonThat(
+ actual: T?
+): ComparableSubject<T> = ComparableSubject(actual = actual, metadata = metadata)
+
+internal fun <T : Throwable> StandardSubjectBuilder.commonThat(actual: T?): ThrowableSubject<T> =
+ ThrowableSubject(actual = actual, metadata = metadata, "throwable")
+
+internal fun StandardSubjectBuilder.commonThat(actual: Boolean?): BooleanSubject =
+ BooleanSubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: Long): LongSubject =
+ LongSubject(actual = actual, metadata = metadata)
+
+// Workaround for https://youtrack.jetbrains.com/issue/KT-645
+internal fun <T : Long?> StandardSubjectBuilder.commonThat(actual: T): LongSubject =
+ LongSubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: Double?): DoubleSubject =
+ DoubleSubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: Float?): FloatSubject =
+ FloatSubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: Int): IntegerSubject =
+ IntegerSubject(actual = actual, metadata = metadata)
+
+// Workaround for https://youtrack.jetbrains.com/issue/KT-645
+internal fun <T : Int?> StandardSubjectBuilder.commonThat(actual: T): IntegerSubject =
+ IntegerSubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: String?): StringSubject =
+ StringSubject(actual = actual, metadata = metadata)
+
+internal fun <T> StandardSubjectBuilder.commonThat(actual: Iterable<T>?): IterableSubject<T> =
+ IterableSubject(actual = actual, metadata = metadata)
+
+internal fun <T> StandardSubjectBuilder.commonThat(actual: Array<out T>?): ObjectArraySubject<T> =
+ ObjectArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(
+ actual: BooleanArray?
+): PrimitiveBooleanArraySubject = PrimitiveBooleanArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: ShortArray?): PrimitiveShortArraySubject =
+ PrimitiveShortArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: IntArray?): PrimitiveIntArraySubject =
+ PrimitiveIntArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: LongArray?): PrimitiveLongArraySubject =
+ PrimitiveLongArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: ByteArray?): PrimitiveByteArraySubject =
+ PrimitiveByteArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: CharArray?): PrimitiveCharArraySubject =
+ PrimitiveCharArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: FloatArray?): PrimitiveFloatArraySubject =
+ PrimitiveFloatArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: DoubleArray?): PrimitiveDoubleArraySubject =
+ PrimitiveDoubleArraySubject(actual = actual, metadata = metadata)
+
+internal fun <K, V> StandardSubjectBuilder.commonThat(actual: Map<K, V>?): MapSubject<K, V> =
+ MapSubject(actual = actual, metadata = metadata)
+
+internal fun <T, S : Subject<T>> StandardSubjectBuilder.commonAbout(
+ subjectFactory: Subject.Factory<S, T>,
+): SimpleSubjectBuilder<S, T> =
+ SimpleSubjectBuilder(metadata = metadata, subjectFactory = subjectFactory)
+
+internal fun StandardSubjectBuilder.commonFail() {
+ metadata.fail()
+}
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt
index ee0a93c..a07fea8 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt
@@ -25,123 +25,55 @@
* @constructor Constructor for use by subclasses. If you want to create an instance of this class
* itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
*/
-open class StringSubject
-protected constructor(
- metadata: FailureMetadata,
- actual: String?,
-) :
- ComparableSubject<String>(actual, metadata),
- PlatformStringSubject by PlatformStringSubjectImpl(actual, metadata) {
+expect open class StringSubject protected constructor(metadata: FailureMetadata, actual: String?) :
+ ComparableSubject<String> {
- internal constructor(actual: String?, metadata: FailureMetadata) : this(metadata, actual)
+ internal constructor(actual: String?, metadata: FailureMetadata)
/** Fails if the string does not contain the given sequence. */
- open fun contains(charSequence: CharSequence) {
- if (actual == null) {
- failWithActual("expected a string that contains", charSequence)
- } else if (!actual.contains(charSequence)) {
- failWithActual("expected to contain", charSequence)
- }
- }
+ open fun contains(charSequence: CharSequence)
/** Fails if the string does not have the given length. */
- open fun hasLength(expectedLength: Int) {
- require(expectedLength >= 0) { "expectedLength($expectedLength) must be >= 0" }
- check("length").that(requireNonNull(actual).length).isEqualTo(expectedLength)
- }
+ open fun hasLength(expectedLength: Int)
/** Fails if the string is not equal to the zero-length "empty string." */
- open fun isEmpty() {
- if (actual == null) {
- failWithActual(simpleFact("expected an empty string"))
- } else if (actual.isNotEmpty()) {
- failWithActual(simpleFact("expected to be string"))
- }
- }
+ open fun isEmpty()
/** Fails if the string is equal to the zero-length "empty string." */
- open fun isNotEmpty() {
- if (actual == null) {
- failWithActual(simpleFact("expected a non-empty string"))
- } else if (actual.isEmpty()) {
- failWithoutActual(simpleFact("expected not to be empty"))
- }
- }
+ open fun isNotEmpty()
/** Fails if the string contains the given sequence. */
- open fun doesNotContain(charSequence: CharSequence) {
- if (actual == null) {
- failWithActual("expected a string that does not contain", charSequence)
- } else if (actual.contains(charSequence)) {
- failWithActual("expected not to contain", charSequence)
- }
- }
+ open fun doesNotContain(charSequence: CharSequence)
/** Fails if the string does not start with the given string. */
- open fun startsWith(string: String) {
- if (actual == null) {
- failWithActual("expected a string that starts with", string)
- } else if (!actual.startsWith(string)) {
- failWithActual("expected to start with", string)
- }
- }
+ open fun startsWith(string: String)
/** Fails if the string does not end with the given string. */
- open fun endsWith(string: String) {
- if (actual == null) {
- failWithActual("expected a string that ends with", string)
- } else if (!actual.endsWith(string)) {
- failWithActual("expected to end with", string)
- }
- }
+ open fun endsWith(string: String)
/** Fails if the string does not match the given [regex]. */
- open fun matches(regex: String) {
- matchesImpl(regex.toRegex()) {
- "Looks like you want to use .isEqualTo() for an exact equality assertion."
- }
- }
+ open fun matches(regex: String)
/** Fails if the string does not match the given [regex]. */
- fun matches(regex: Regex) {
- matchesImpl(regex) {
- "If you want an exact equality assertion you can escape your regex with Regex.escape()."
- }
- }
+ fun matches(regex: Regex)
/** Fails if the string matches the given regex. */
- open fun doesNotMatch(regex: String) {
- doesNotMatchImpl(regex.toRegex())
- }
+ open fun doesNotMatch(regex: String)
/** Fails if the string matches the given regex. */
- fun doesNotMatch(regex: Regex) {
- doesNotMatchImpl(regex)
- }
+ fun doesNotMatch(regex: Regex)
/** Fails if the string does not contain a match on the given regex. */
- open fun containsMatch(regex: String) {
- containsMatchImpl(regex.toRegex())
- }
+ open fun containsMatch(regex: String)
/** Fails if the string does not contain a match on the given regex. */
- fun containsMatch(regex: Regex) {
- containsMatchImpl(regex)
- }
+ fun containsMatch(regex: Regex)
/** Fails if the string contains a match on the given regex. */
- open fun doesNotContainMatch(regex: String) {
- if (actual == null) {
- failWithActual("expected a string that does not contain a match for", regex)
- } else if (regex.toRegex().containsMatchIn(actual)) {
- failWithActual("expected not to contain a match for", regex)
- }
- }
+ open fun doesNotContainMatch(regex: String)
/** Fails if the string contains a match on the given regex. */
- fun doesNotContainMatch(regex: Regex) {
- doesNotContainMatchImpl(regex)
- }
+ fun doesNotContainMatch(regex: Regex)
/**
* Returns a [StringSubject]-like instance that will ignore the case of the characters.
@@ -150,7 +82,7 @@
* calling [Char.lowercaseChar] or after calling [Char.uppercaseChar]. Note that this is
* independent of any locale.
*/
- open fun ignoringCase(): CaseInsensitiveStringComparison = CaseInsensitiveStringComparison()
+ open fun ignoringCase(): CaseInsensitiveStringComparison
inner class CaseInsensitiveStringComparison internal constructor() {
/**
@@ -161,82 +93,205 @@
*
* Example: "abc" is equal to "ABC", but not to "abcd".
*/
- fun isEqualTo(expected: String?) {
- if ((actual == null) && (expected != null)) {
- failWithoutActual(
- fact("expected a string that is equal to", expected),
- fact("but was", actual),
- simpleFact("(case is ignored)")
- )
- } else if ((expected == null) && (actual != null)) {
- failWithoutActual(
- fact("expected", "null (null reference)"),
- fact("but was", actual),
- simpleFact("(case is ignored)")
- )
- } else if (!actual.equals(expected, ignoreCase = true)) {
- failWithoutActual(
- fact("expected", expected),
- fact("but was", actual),
- simpleFact("(case is ignored)")
- )
- }
- }
+ fun isEqualTo(expected: String?)
/**
* Fails if the subject is equal to the given string (while ignoring case). The meaning of
* equality is the same as for the [isEqualTo] method.
*/
- fun isNotEqualTo(unexpected: String?) {
- if ((actual == null) && (unexpected == null)) {
- failWithoutActual(
- fact("expected a string that is not equal to", "null (null reference)"),
- simpleFact("(case is ignored)")
- )
- } else if (actual.equals(unexpected, ignoreCase = true)) {
- failWithoutActual(
- fact("expected not to be", unexpected),
- fact("but was", actual),
- simpleFact("(case is ignored)")
- )
- }
- }
+ fun isNotEqualTo(unexpected: String?)
/** Fails if the string does not contain the given sequence (while ignoring case). */
- fun contains(expected: CharSequence?) {
- requireNonNull(expected)
-
- if (actual == null) {
- failWithoutActual(
- fact("expected a string that contains", expected),
- fact("but was", actual),
- simpleFact("(case is ignored)")
- )
- } else if (!actual.contains(expected, ignoreCase = true)) {
- failWithoutActual(
- fact("expected to contain", expected),
- fact("but was", actual),
- simpleFact("(case is ignored)")
- )
- }
- }
+ fun contains(expected: CharSequence?)
/** Fails if the string contains the given sequence (while ignoring case). */
- fun doesNotContain(expected: CharSequence) {
- if (actual == null) {
- failWithoutActual(
- fact("expected a string that does not contain", expected),
- fact("but was", actual),
- simpleFact("(case is ignored)")
- )
- } else if (actual.contains(expected, ignoreCase = true)) {
- failWithoutActual(
- fact("expected not to contain", expected),
- fact("but was", actual),
- simpleFact("(case is ignored)")
- )
- }
- }
+ fun doesNotContain(expected: CharSequence)
+ }
+}
+
+/** Fails if the string does not contain the given sequence. */
+internal fun StringSubject.commonContains(charSequence: CharSequence) {
+ if (actual == null) {
+ failWithActualInternal("expected a string that contains", charSequence)
+ } else if (!actual.contains(charSequence)) {
+ failWithActualInternal("expected to contain", charSequence)
+ }
+}
+
+/** Fails if the string does not have the given length. */
+internal fun StringSubject.commonHasLength(expectedLength: Int) {
+ require(expectedLength >= 0) { "expectedLength($expectedLength) must be >= 0" }
+ checkInternal("length").that(requireNonNull(actual).length).isEqualTo(expectedLength)
+}
+
+/** Fails if the string is not equal to the zero-length "empty string." */
+internal fun StringSubject.commonIsEmpty() {
+ if (actual == null) {
+ failWithActualInternal(simpleFact("expected an empty string"))
+ } else if (actual.isNotEmpty()) {
+ failWithActualInternal(simpleFact("expected to be string"))
+ }
+}
+
+/** Fails if the string is equal to the zero-length "empty string." */
+internal fun StringSubject.commonIsNotEmpty() {
+ if (actual == null) {
+ failWithActualInternal(simpleFact("expected a non-empty string"))
+ } else if (actual.isEmpty()) {
+ failWithoutActualInternal(simpleFact("expected not to be empty"))
+ }
+}
+
+/** Fails if the string contains the given sequence. */
+internal fun StringSubject.commonDoesNotContain(charSequence: CharSequence) {
+ if (actual == null) {
+ failWithActualInternal("expected a string that does not contain", charSequence)
+ } else if (actual.contains(charSequence)) {
+ failWithActualInternal("expected not to contain", charSequence)
+ }
+}
+
+/** Fails if the string does not start with the given string. */
+internal fun StringSubject.commonStartsWith(string: String) {
+ if (actual == null) {
+ failWithActualInternal("expected a string that starts with", string)
+ } else if (!actual.startsWith(string)) {
+ failWithActualInternal("expected to start with", string)
+ }
+}
+
+/** Fails if the string does not end with the given string. */
+internal fun StringSubject.commonEndsWith(string: String) {
+ if (actual == null) {
+ failWithActualInternal("expected a string that ends with", string)
+ } else if (!actual.endsWith(string)) {
+ failWithActualInternal("expected to end with", string)
+ }
+}
+
+/** Fails if the string does not match the given [regex]. */
+internal fun StringSubject.commonMatches(regex: String) {
+ matchesImpl(regex.toRegex()) {
+ "Looks like you want to use .isEqualTo() for an exact equality assertion."
+ }
+}
+
+/** Fails if the string does not match the given [regex]. */
+internal fun StringSubject.commonMatches(regex: Regex) {
+ matchesImpl(regex) {
+ "If you want an exact equality assertion you can escape your regex with Regex.escape()."
+ }
+}
+
+/** Fails if the string matches the given regex. */
+internal fun StringSubject.commonDoesNotMatch(regex: String) {
+ doesNotMatchImpl(regex.toRegex())
+}
+
+/** Fails if the string matches the given regex. */
+internal fun StringSubject.commonDoesNotMatch(regex: Regex) {
+ doesNotMatchImpl(regex)
+}
+
+/** Fails if the string does not contain a match on the given regex. */
+internal fun StringSubject.commonContainsMatch(regex: String) {
+ containsMatchImpl(regex.toRegex())
+}
+
+/** Fails if the string does not contain a match on the given regex. */
+internal fun StringSubject.commonContainsMatch(regex: Regex) {
+ containsMatchImpl(regex)
+}
+
+/** Fails if the string contains a match on the given regex. */
+internal fun StringSubject.commonDoesNotContainMatch(regex: String) {
+ if (actual == null) {
+ failWithActualInternal("expected a string that does not contain a match for", regex)
+ } else if (regex.toRegex().containsMatchIn(actual)) {
+ failWithActualInternal("expected not to contain a match for", regex)
+ }
+}
+
+/** Fails if the string contains a match on the given regex. */
+internal fun StringSubject.commonDoesNotContainMatch(regex: Regex) {
+ doesNotContainMatchImpl(regex)
+}
+
+internal fun StringSubject.commonIgnoringCase(): StringSubject.CaseInsensitiveStringComparison =
+ CaseInsensitiveStringComparison()
+
+internal fun StringSubject.commonCaseInsensitiveStringComparisonIsEqualTo(expected: String?) {
+ if ((actual == null) && (expected != null)) {
+ failWithoutActualInternal(
+ fact("expected a string that is equal to", expected),
+ fact("but was", actual),
+ simpleFact("(case is ignored)")
+ )
+ } else if ((expected == null) && (actual != null)) {
+ failWithoutActualInternal(
+ fact("expected", "null (null reference)"),
+ fact("but was", actual),
+ simpleFact("(case is ignored)")
+ )
+ } else if (!actual.equals(expected, ignoreCase = true)) {
+ failWithoutActualInternal(
+ fact("expected", expected),
+ fact("but was", actual),
+ simpleFact("(case is ignored)")
+ )
+ }
+}
+
+internal fun StringSubject.commonCaseInsensitiveStringComparisonIsNotEqualTo(unexpected: String?) {
+ if ((actual == null) && (unexpected == null)) {
+ failWithoutActualInternal(
+ fact("expected a string that is not equal to", "null (null reference)"),
+ simpleFact("(case is ignored)")
+ )
+ } else if (actual.equals(unexpected, ignoreCase = true)) {
+ failWithoutActualInternal(
+ fact("expected not to be", unexpected),
+ fact("but was", actual),
+ simpleFact("(case is ignored)")
+ )
+ }
+}
+
+/** Fails if the string does not contain the given sequence (while ignoring case). */
+internal fun StringSubject.commonCaseInsensitiveStringComparisonContains(expected: CharSequence?) {
+ requireNonNull(expected)
+
+ if (actual == null) {
+ failWithoutActualInternal(
+ fact("expected a string that contains", expected),
+ fact("but was", actual),
+ simpleFact("(case is ignored)")
+ )
+ } else if (!actual.contains(expected, ignoreCase = true)) {
+ failWithoutActualInternal(
+ fact("expected to contain", expected),
+ fact("but was", actual),
+ simpleFact("(case is ignored)")
+ )
+ }
+}
+
+/** Fails if the string contains the given sequence (while ignoring case). */
+internal fun StringSubject.commonCaseInsensitiveStringComparisonDoesNotContain(
+ expected: CharSequence
+) {
+ if (actual == null) {
+ failWithoutActualInternal(
+ fact("expected a string that does not contain", expected),
+ fact("but was", actual),
+ simpleFact("(case is ignored)")
+ )
+ } else if (actual.contains(expected, ignoreCase = true)) {
+ failWithoutActualInternal(
+ fact("expected not to contain", expected),
+ fact("but was", actual),
+ simpleFact("(case is ignored)")
+ )
}
}
@@ -289,10 +344,3 @@
)
}
}
-
-internal expect interface PlatformStringSubject
-
-internal expect class PlatformStringSubjectImpl(
- actual: String?,
- metadata: FailureMetadata,
-) : Subject<String>, PlatformStringSubject
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt
index 5901926..d58fdbc 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt
@@ -357,6 +357,10 @@
else -> toString()
}
+ @Suppress("NOTHING_TO_INLINE")
+ internal inline fun checkInternal(format: String, vararg args: Any): StandardSubjectBuilder =
+ check(format, *args)
+
/**
* Returns a builder for creating a derived subject.
*
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/ComparableSubject.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/ComparableSubject.jvm.kt
index c507ac6..1be5df0 100644
--- a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/ComparableSubject.jvm.kt
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/ComparableSubject.jvm.kt
@@ -20,32 +20,63 @@
import com.google.common.collect.Range
/**
- * Platform-specific propositions for [Comparable] typed subjects.
+ * Propositions for [Comparable] typed subjects.
*
* @param T the type of the object being tested by this [ComparableSubject]
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ * itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
*/
-internal actual interface PlatformComparableSubject<T : Comparable<T>> {
+actual open class ComparableSubject<T : Comparable<T>>
+protected actual constructor(metadata: FailureMetadata, actual: T?) :
+ Subject<T>(actual, metadata, typeDescriptionOverride = null) {
+ internal actual constructor(actual: T?, metadata: FailureMetadata) : this(metadata, actual)
+
+ /**
+ * Checks that the subject is equivalent to [other] according to [Comparable.compareTo], (i.e.,
+ * checks that `a.comparesTo(b) == 0`).
+ *
+ * **Note:** Do not use this method for checking object equality. Instead, use [isEqualTo].
+ */
+ actual open fun isEquivalentAccordingToCompareTo(other: T?) =
+ commonIsEquivalentAccordingToCompareTo(other)
+
+ /**
+ * Checks that the subject is greater than [other].
+ *
+ * To check that the subject is greater than *or equal to* [other], use [isAtLeast].
+ */
+ actual fun isGreaterThan(other: T?) = commonIsGreaterThan(other)
+
+ /**
+ * Checks that the subject is less than [other].
+ *
+ * @throws NullPointerException if [actual] or [other] is `null`.
+ */
+ actual fun isLessThan(other: T?) = commonIsLessThan(other)
+
+ /**
+ * Checks that the subject is less than or equal to [other].
+ *
+ * @throws NullPointerException if [actual] or [other] is `null`.
+ */
+ actual fun isAtMost(other: T?) = commonIsAtMost(other)
+
+ /**
+ * Checks that the subject is greater than or equal to [other].
+ *
+ * @throws NullPointerException if [actual] or [other] is `null`.
+ */
+ actual fun isAtLeast(other: T?) = commonIsAtLeast(other)
/** Checks that the subject is in [range]. */
- fun isIn(range: Range<T>)
-
- /** Checks that the subject is *not* in [range]. */
- fun isNotIn(range: Range<T>)
-}
-
-internal actual class PlatformComparableSubjectImpl<T : Comparable<T>>
-actual constructor(
- actual: T?,
- metadata: FailureMetadata,
-) : Subject<T>(actual, metadata, typeDescriptionOverride = null), PlatformComparableSubject<T> {
-
- override fun isIn(range: Range<T>) {
+ fun isIn(range: Range<T>) {
if (requireNonNull(actual) !in range) {
failWithoutActualInternal(fact("Expected to be in range", range))
}
}
- override fun isNotIn(range: Range<T>) {
+ /** Checks that the subject is *not* in [range]. */
+ fun isNotIn(range: Range<T>) {
if (requireNonNull(actual) in range) {
failWithoutActualInternal(fact("Expected not to be in range", range))
}
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.jvm.kt
deleted file mode 100644
index 98742fa..0000000
--- a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.jvm.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.kruth
-
-import com.google.common.base.Optional
-import com.google.common.collect.Multimap
-import com.google.common.collect.Multiset
-import com.google.common.collect.Table
-import java.math.BigDecimal
-
-internal actual interface PlatformStandardSubjectBuilder {
-
- fun that(actual: Class<*>): ClassSubject
-
- fun <T : Any> that(actual: Optional<T>): GuavaOptionalSubject<T>
-
- fun that(actual: BigDecimal): BigDecimalSubject
-
- fun <T> that(actual: Multiset<T>): MultisetSubject<T>
-
- fun <K, V> that(actual: Multimap<K, V>): MultimapSubject<K, V>
-
- fun <R, C, V> that(actual: Table<R, C, V>): TableSubject<R, C, V>
-}
-
-internal actual class PlatformStandardSubjectBuilderImpl
-actual constructor(
- private val metadata: FailureMetadata,
-) : PlatformStandardSubjectBuilder {
-
- override fun that(actual: Class<*>): ClassSubject =
- ClassSubject(actual = actual, metadata = metadata)
-
- override fun <T : Any> that(actual: Optional<T>): GuavaOptionalSubject<T> =
- GuavaOptionalSubject(actual = actual, metadata = metadata)
-
- override fun that(actual: BigDecimal): BigDecimalSubject =
- BigDecimalSubject(actual = actual, metadata = metadata)
-
- override fun <T> that(actual: Multiset<T>): MultisetSubject<T> =
- MultisetSubject(actual = actual, metadata = metadata)
-
- override fun <K, V> that(actual: Multimap<K, V>): MultimapSubject<K, V> =
- MultimapSubject(actual = actual, metadata = metadata)
-
- override fun <R, C, V> that(actual: Table<R, C, V>): TableSubject<R, C, V> =
- TableSubject(actual = actual, metadata = metadata)
-}
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStringSubject.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStringSubject.jvm.kt
deleted file mode 100644
index 8f7f6ed..0000000
--- a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStringSubject.jvm.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.kruth
-
-import java.util.regex.Pattern
-
-internal actual interface PlatformStringSubject {
-
- /** Fails if the string does not match the given regex. */
- fun matches(regex: Pattern)
-
- /** Fails if the string matches the given regex. */
- fun doesNotMatch(regex: Pattern)
-
- /** Fails if the string does not contain a match on the given regex. */
- fun containsMatch(regex: Pattern)
-
- /** Fails if the string contains a match on the given regex. */
- fun doesNotContainMatch(regex: Pattern)
-}
-
-internal actual class PlatformStringSubjectImpl
-actual constructor(
- actual: String?,
- metadata: FailureMetadata,
-) : Subject<String>(actual, metadata, typeDescriptionOverride = null), PlatformStringSubject {
-
- override fun matches(regex: Pattern) {
- matchesImpl(regex.toRegex()) {
- "If you want an exact equality assertion you can escape your regex with " +
- "Pattern.quote()."
- }
- }
-
- override fun doesNotMatch(regex: Pattern) {
- doesNotMatchImpl(regex.toRegex())
- }
-
- override fun containsMatch(regex: Pattern) {
- containsMatchImpl(regex.toRegex())
- }
-
- override fun doesNotContainMatch(regex: Pattern) {
- doesNotContainMatchImpl(regex.toRegex())
- }
-}
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StandardSubjectBuilder.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StandardSubjectBuilder.jvm.kt
new file mode 100644
index 0000000..9f688ff
--- /dev/null
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StandardSubjectBuilder.jvm.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.kruth
+
+import com.google.common.base.Optional
+import com.google.common.collect.Multimap
+import com.google.common.collect.Multiset
+import com.google.common.collect.Table
+import java.math.BigDecimal
+
+/**
+ * In a fluent assertion chain, an object with which you can do any of the following:
+ * - Set an optional message with [withMessage].
+ * - For the types of [Subject] built into Kruth, directly specify the value under test with
+ * [withMessage].
+ */
+@Suppress("StaticFinalBuilder") // Cannot be final for binary compatibility.
+actual open class StandardSubjectBuilder internal actual constructor(metadata: FailureMetadata) {
+ internal actual val metadata: FailureMetadata = metadata
+ get() {
+ checkStatePreconditions()
+ return field
+ }
+
+ actual companion object {
+ /** Returns a new instance that invokes the given [FailureStrategy] when a check fails. */
+ @JvmStatic
+ actual fun forCustomFailureStrategy(
+ failureStrategy: FailureStrategy
+ ): StandardSubjectBuilder = commonForCustomFailureStrategy(failureStrategy)
+ }
+
+ /**
+ * Returns a new instance that will output the given message before the main failure message. If
+ * this method is called multiple times, the messages will appear in the order that they were
+ * specified.
+ */
+ actual fun withMessage(messageToPrepend: String): StandardSubjectBuilder =
+ commonWithMessage(messageToPrepend)
+
+ actual fun <T> that(actual: T?): Subject<T> = commonThat(actual)
+
+ actual fun that(actual: Char): Subject<Char> = commonThat(actual)
+
+ actual fun <T : Comparable<T>> that(actual: T?): ComparableSubject<T> = commonThat(actual)
+
+ actual fun <T : Throwable> that(actual: T?): ThrowableSubject<T> = commonThat(actual)
+
+ actual fun that(actual: Boolean?): BooleanSubject = commonThat(actual)
+
+ actual fun that(actual: Long): LongSubject = commonThat(actual)
+
+ actual fun <T : Long?> that(actual: T): LongSubject = commonThat(actual)
+
+ actual fun that(actual: Double?): DoubleSubject = commonThat(actual)
+
+ actual fun that(actual: Float?): FloatSubject = commonThat(actual)
+
+ actual fun that(actual: Int): IntegerSubject = commonThat(actual)
+
+ actual fun <T : Int?> that(actual: T): IntegerSubject = commonThat(actual)
+
+ actual fun that(actual: String?): StringSubject = commonThat(actual)
+
+ actual fun <T> that(actual: Iterable<T>?): IterableSubject<T> = commonThat(actual)
+
+ actual fun <T> that(actual: Array<out T>?): ObjectArraySubject<T> = commonThat(actual)
+
+ actual fun that(actual: BooleanArray?): PrimitiveBooleanArraySubject = commonThat(actual)
+
+ actual fun that(actual: ShortArray?): PrimitiveShortArraySubject = commonThat(actual)
+
+ actual fun that(actual: IntArray?): PrimitiveIntArraySubject = commonThat(actual)
+
+ actual fun that(actual: LongArray?): PrimitiveLongArraySubject = commonThat(actual)
+
+ actual fun that(actual: ByteArray?): PrimitiveByteArraySubject = commonThat(actual)
+
+ actual fun that(actual: CharArray?): PrimitiveCharArraySubject = commonThat(actual)
+
+ actual fun that(actual: FloatArray?): PrimitiveFloatArraySubject = commonThat(actual)
+
+ actual fun that(actual: DoubleArray?): PrimitiveDoubleArraySubject = commonThat(actual)
+
+ actual fun <K, V> that(actual: Map<K, V>?): MapSubject<K, V> = commonThat(actual)
+
+ /**
+ * Given a factory for some [Subject] class, returns [SimpleSubjectBuilder] whose
+ * [that][SimpleSubjectBuilder.that] method creates instances of that class. Created subjects
+ * use the previously set failure strategy and any previously set failure message.
+ */
+ actual fun <T, S : Subject<T>> about(
+ subjectFactory: Subject.Factory<S, T>
+ ): SimpleSubjectBuilder<S, T> = commonAbout(subjectFactory)
+
+ /**
+ * Reports a failure.
+ *
+ * To set a message, first call [withMessage] (or, more commonly, use the shortcut
+ * [assertWithMessage].
+ */
+ actual fun fail() = commonFail()
+
+ internal actual open fun checkStatePreconditions() {}
+
+ @Suppress("BuilderSetStyle") // Necessary for compatibility
+ fun that(actual: Class<*>): ClassSubject = ClassSubject(actual = actual, metadata = metadata)
+
+ @Suppress("BuilderSetStyle") // Necessary for compatibility
+ fun <T : Any> that(actual: Optional<T>): GuavaOptionalSubject<T> =
+ GuavaOptionalSubject(actual = actual, metadata = metadata)
+
+ @Suppress("BuilderSetStyle") // Necessary for compatibility
+ fun that(actual: BigDecimal): BigDecimalSubject =
+ BigDecimalSubject(actual = actual, metadata = metadata)
+
+ @Suppress("BuilderSetStyle") // Necessary for compatibility
+ fun <T> that(actual: Multiset<T>): MultisetSubject<T> =
+ MultisetSubject(actual = actual, metadata = metadata)
+
+ @Suppress("BuilderSetStyle") // Necessary for compatibility
+ fun <K, V> that(actual: Multimap<K, V>): MultimapSubject<K, V> =
+ MultimapSubject(actual = actual, metadata = metadata)
+
+ @Suppress("BuilderSetStyle") // Necessary for compatibility
+ fun <R, C, V> that(actual: Table<R, C, V>): TableSubject<R, C, V> =
+ TableSubject(actual = actual, metadata = metadata)
+}
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StringSubject.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StringSubject.jvm.kt
new file mode 100644
index 0000000..4345487
--- /dev/null
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StringSubject.jvm.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.kruth
+
+import java.util.regex.Pattern
+
+/**
+ * Propositions for [String] subjects.
+ *
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ * itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
+ */
+actual open class StringSubject
+protected actual constructor(metadata: FailureMetadata, actual: String?) :
+ ComparableSubject<String>(actual, metadata) {
+
+ internal actual constructor(actual: String?, metadata: FailureMetadata) : this(metadata, actual)
+
+ /** Fails if the string does not contain the given sequence. */
+ actual open fun contains(charSequence: CharSequence) = commonContains(charSequence)
+
+ /** Fails if the string does not have the given length. */
+ actual open fun hasLength(expectedLength: Int) = commonHasLength(expectedLength)
+
+ /** Fails if the string is not equal to the zero-length "empty string." */
+ actual open fun isEmpty() = commonIsEmpty()
+
+ /** Fails if the string is equal to the zero-length "empty string." */
+ actual open fun isNotEmpty() = commonIsNotEmpty()
+
+ /** Fails if the string contains the given sequence. */
+ actual open fun doesNotContain(charSequence: CharSequence) = commonDoesNotContain(charSequence)
+
+ /** Fails if the string does not start with the given string. */
+ actual open fun startsWith(string: String) = commonStartsWith(string)
+
+ /** Fails if the string does not end with the given string. */
+ actual open fun endsWith(string: String) = commonEndsWith(string)
+
+ /** Fails if the string does not match the given [regex]. */
+ actual open fun matches(regex: String) = commonMatches(regex)
+
+ /** Fails if the string does not match the given [regex]. */
+ actual fun matches(regex: Regex) = commonMatches(regex)
+
+ /** Fails if the string matches the given regex. */
+ actual open fun doesNotMatch(regex: String) = commonDoesNotMatch(regex)
+
+ /** Fails if the string matches the given regex. */
+ actual fun doesNotMatch(regex: Regex) = commonDoesNotMatch(regex)
+
+ /** Fails if the string does not contain a match on the given regex. */
+ actual open fun containsMatch(regex: String) = commonContainsMatch(regex)
+
+ /** Fails if the string does not contain a match on the given regex. */
+ actual fun containsMatch(regex: Regex) = commonContainsMatch(regex)
+
+ /** Fails if the string contains a match on the given regex. */
+ actual open fun doesNotContainMatch(regex: String) = commonDoesNotContainMatch(regex)
+
+ /** Fails if the string contains a match on the given regex. */
+ actual fun doesNotContainMatch(regex: Regex) = commonDoesNotContainMatch(regex)
+
+ /**
+ * Returns a [StringSubject]-like instance that will ignore the case of the characters.
+ *
+ * Character equality ignoring case is defined as follows: Characters must be equal either after
+ * calling [Char.lowercaseChar] or after calling [Char.uppercaseChar]. Note that this is
+ * independent of any locale.
+ */
+ actual open fun ignoringCase(): CaseInsensitiveStringComparison = commonIgnoringCase()
+
+ /** Fails if the string does not match the given regex. */
+ open fun matches(regex: Pattern) {
+ matchesImpl(regex.toRegex()) {
+ "If you want an exact equality assertion you can escape your regex with " +
+ "Pattern.quote()."
+ }
+ }
+
+ /** Fails if the string matches the given regex. */
+ open fun doesNotMatch(regex: Pattern) {
+ doesNotMatchImpl(regex.toRegex())
+ }
+
+ /** Fails if the string does not contain a match on the given regex. */
+ open fun containsMatch(regex: Pattern) {
+ containsMatchImpl(regex.toRegex())
+ }
+
+ /** Fails if the string contains a match on the given regex. */
+ open fun doesNotContainMatch(regex: Pattern) {
+ doesNotContainMatchImpl(regex.toRegex())
+ }
+
+ actual inner class CaseInsensitiveStringComparison internal actual constructor() {
+ /**
+ * Fails if the subject is not equal to the given sequence (while ignoring case). For the
+ * purposes of this comparison, two strings are equal if any of the following is true:
+ * * they are equal according to [String.equals] with `ignoreCase = true`
+ * * they are both null
+ *
+ * Example: "abc" is equal to "ABC", but not to "abcd".
+ */
+ actual fun isEqualTo(expected: String?) =
+ commonCaseInsensitiveStringComparisonIsEqualTo(expected)
+
+ /**
+ * Fails if the subject is equal to the given string (while ignoring case). The meaning of
+ * equality is the same as for the [isEqualTo] method.
+ */
+ actual fun isNotEqualTo(unexpected: String?) =
+ commonCaseInsensitiveStringComparisonIsNotEqualTo(unexpected)
+
+ /** Fails if the string does not contain the given sequence (while ignoring case). */
+ actual fun contains(expected: CharSequence?) =
+ commonCaseInsensitiveStringComparisonContains(expected)
+
+ /** Fails if the string contains the given sequence (while ignoring case). */
+ actual fun doesNotContain(expected: CharSequence) =
+ commonCaseInsensitiveStringComparisonDoesNotContain(expected)
+ }
+}
diff --git a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/ComparableSubject.native.kt b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/ComparableSubject.native.kt
index 9093e27..bdea756 100644
--- a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/ComparableSubject.native.kt
+++ b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/ComparableSubject.native.kt
@@ -17,14 +17,52 @@
package androidx.kruth
/**
- * Platform-specific propositions for [Comparable] typed subjects.
+ * Propositions for [Comparable] typed subjects.
*
* @param T the type of the object being tested by this [ComparableSubject]
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ * itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
*/
-internal actual interface PlatformComparableSubject<T : Comparable<T>>
+actual open class ComparableSubject<T : Comparable<T>>
+protected actual constructor(metadata: FailureMetadata, actual: T?) :
+ Subject<T>(actual, metadata, typeDescriptionOverride = null) {
-internal actual class PlatformComparableSubjectImpl<T : Comparable<T>>
-actual constructor(
- actual: T?,
- metadata: FailureMetadata,
-) : Subject<T>(actual, metadata, typeDescriptionOverride = null), PlatformComparableSubject<T>
+ internal actual constructor(actual: T?, metadata: FailureMetadata) : this(metadata, actual)
+
+ /**
+ * Checks that the subject is equivalent to [other] according to [Comparable.compareTo], (i.e.,
+ * checks that `a.comparesTo(b) == 0`).
+ *
+ * **Note:** Do not use this method for checking object equality. Instead, use [isEqualTo].
+ */
+ actual open fun isEquivalentAccordingToCompareTo(other: T?) =
+ commonIsEquivalentAccordingToCompareTo(other)
+
+ /**
+ * Checks that the subject is greater than [other].
+ *
+ * To check that the subject is greater than *or equal to* [other], use [isAtLeast].
+ */
+ actual fun isGreaterThan(other: T?) = commonIsGreaterThan(other)
+
+ /**
+ * Checks that the subject is less than [other].
+ *
+ * @throws NullPointerException if [actual] or [other] is `null`.
+ */
+ actual fun isLessThan(other: T?) = commonIsLessThan(other)
+
+ /**
+ * Checks that the subject is less than or equal to [other].
+ *
+ * @throws NullPointerException if [actual] or [other] is `null`.
+ */
+ actual fun isAtMost(other: T?) = commonIsAtMost(other)
+
+ /**
+ * Checks that the subject is greater than or equal to [other].
+ *
+ * @throws NullPointerException if [actual] or [other] is `null`.
+ */
+ actual fun isAtLeast(other: T?) = commonIsAtLeast(other)
+}
diff --git a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStringSubject.kt b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStringSubject.kt
deleted file mode 100644
index afcd294..0000000
--- a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStringSubject.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.kruth
-
-internal actual interface PlatformStringSubject
-
-internal actual class PlatformStringSubjectImpl
-actual constructor(
- actual: String?,
- metadata: FailureMetadata,
-) : Subject<String>(actual, metadata, typeDescriptionOverride = null), PlatformStringSubject
diff --git a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/StandardSubjectBuilder.native.kt b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/StandardSubjectBuilder.native.kt
new file mode 100644
index 0000000..ed09dad
--- /dev/null
+++ b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/StandardSubjectBuilder.native.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.kruth
+
+/**
+ * In a fluent assertion chain, an object with which you can do any of the following:
+ * - Set an optional message with [withMessage].
+ * - For the types of [Subject] built into Kruth, directly specify the value under test with
+ * [withMessage].
+ */
+@Suppress("StaticFinalBuilder") // Cannot be final for binary compatibility.
+actual open class StandardSubjectBuilder internal actual constructor(metadata: FailureMetadata) {
+ internal actual val metadata: FailureMetadata = metadata
+ get() {
+ checkStatePreconditions()
+ return field
+ }
+
+ actual companion object {
+ /** Returns a new instance that invokes the given [FailureStrategy] when a check fails. */
+ actual fun forCustomFailureStrategy(
+ failureStrategy: FailureStrategy
+ ): StandardSubjectBuilder = commonForCustomFailureStrategy(failureStrategy)
+ }
+
+ /**
+ * Returns a new instance that will output the given message before the main failure message. If
+ * this method is called multiple times, the messages will appear in the order that they were
+ * specified.
+ */
+ actual fun withMessage(messageToPrepend: String): StandardSubjectBuilder =
+ commonWithMessage(messageToPrepend)
+
+ actual fun <T> that(actual: T?): Subject<T> = commonThat(actual)
+
+ actual fun that(actual: Char): Subject<Char> = commonThat(actual)
+
+ actual fun <T : Comparable<T>> that(actual: T?): ComparableSubject<T> = commonThat(actual)
+
+ actual fun <T : Throwable> that(actual: T?): ThrowableSubject<T> = commonThat(actual)
+
+ actual fun that(actual: Boolean?): BooleanSubject = commonThat(actual)
+
+ actual fun that(actual: Long): LongSubject = commonThat(actual)
+
+ actual fun <T : Long?> that(actual: T): LongSubject = commonThat(actual)
+
+ actual fun that(actual: Double?): DoubleSubject = commonThat(actual)
+
+ actual fun that(actual: Float?): FloatSubject = commonThat(actual)
+
+ actual fun that(actual: Int): IntegerSubject = commonThat(actual)
+
+ actual fun <T : Int?> that(actual: T): IntegerSubject = commonThat(actual)
+
+ actual fun that(actual: String?): StringSubject = commonThat(actual)
+
+ actual fun <T> that(actual: Iterable<T>?): IterableSubject<T> = commonThat(actual)
+
+ actual fun <T> that(actual: Array<out T>?): ObjectArraySubject<T> = commonThat(actual)
+
+ actual fun that(actual: BooleanArray?): PrimitiveBooleanArraySubject = commonThat(actual)
+
+ actual fun that(actual: ShortArray?): PrimitiveShortArraySubject = commonThat(actual)
+
+ actual fun that(actual: IntArray?): PrimitiveIntArraySubject = commonThat(actual)
+
+ actual fun that(actual: LongArray?): PrimitiveLongArraySubject = commonThat(actual)
+
+ actual fun that(actual: ByteArray?): PrimitiveByteArraySubject = commonThat(actual)
+
+ actual fun that(actual: CharArray?): PrimitiveCharArraySubject = commonThat(actual)
+
+ actual fun that(actual: FloatArray?): PrimitiveFloatArraySubject = commonThat(actual)
+
+ actual fun that(actual: DoubleArray?): PrimitiveDoubleArraySubject = commonThat(actual)
+
+ actual fun <K, V> that(actual: Map<K, V>?): MapSubject<K, V> = commonThat(actual)
+
+ /**
+ * Given a factory for some [Subject] class, returns [SimpleSubjectBuilder] whose
+ * [that][SimpleSubjectBuilder.that] method creates instances of that class. Created subjects
+ * use the previously set failure strategy and any previously set failure message.
+ */
+ actual fun <T, S : Subject<T>> about(
+ subjectFactory: Subject.Factory<S, T>
+ ): SimpleSubjectBuilder<S, T> = commonAbout(subjectFactory)
+
+ /**
+ * Reports a failure.
+ *
+ * To set a message, first call [withMessage] (or, more commonly, use the shortcut
+ * [assertWithMessage].
+ */
+ actual fun fail() = commonFail()
+
+ internal actual open fun checkStatePreconditions() {}
+}
diff --git a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/StringSubject.native.kt b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/StringSubject.native.kt
new file mode 100644
index 0000000..4759796
--- /dev/null
+++ b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/StringSubject.native.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.kruth
+
+/**
+ * Propositions for [String] subjects.
+ *
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ * itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
+ */
+actual open class StringSubject
+protected actual constructor(metadata: FailureMetadata, actual: String?) :
+ ComparableSubject<String>(actual, metadata) {
+
+ internal actual constructor(actual: String?, metadata: FailureMetadata) : this(metadata, actual)
+
+ /** Fails if the string does not contain the given sequence. */
+ actual open fun contains(charSequence: CharSequence) = commonContains(charSequence)
+
+ /** Fails if the string does not have the given length. */
+ actual open fun hasLength(expectedLength: Int) = commonHasLength(expectedLength)
+
+ /** Fails if the string is not equal to the zero-length "empty string." */
+ actual open fun isEmpty() = commonIsEmpty()
+
+ /** Fails if the string is equal to the zero-length "empty string." */
+ actual open fun isNotEmpty() = commonIsNotEmpty()
+
+ /** Fails if the string contains the given sequence. */
+ actual open fun doesNotContain(charSequence: CharSequence) = commonDoesNotContain(charSequence)
+
+ /** Fails if the string does not start with the given string. */
+ actual open fun startsWith(string: String) = commonStartsWith(string)
+
+ /** Fails if the string does not end with the given string. */
+ actual open fun endsWith(string: String) = commonEndsWith(string)
+
+ /** Fails if the string does not match the given [regex]. */
+ actual open fun matches(regex: String) = commonMatches(regex)
+
+ /** Fails if the string does not match the given [regex]. */
+ actual fun matches(regex: Regex) = commonMatches(regex)
+
+ /** Fails if the string matches the given regex. */
+ actual open fun doesNotMatch(regex: String) = commonDoesNotMatch(regex)
+
+ /** Fails if the string matches the given regex. */
+ actual fun doesNotMatch(regex: Regex) = commonDoesNotMatch(regex)
+
+ /** Fails if the string does not contain a match on the given regex. */
+ actual open fun containsMatch(regex: String) = commonContainsMatch(regex)
+
+ /** Fails if the string does not contain a match on the given regex. */
+ actual fun containsMatch(regex: Regex) = commonContainsMatch(regex)
+
+ /** Fails if the string contains a match on the given regex. */
+ actual open fun doesNotContainMatch(regex: String) = commonDoesNotContainMatch(regex)
+
+ /** Fails if the string contains a match on the given regex. */
+ actual fun doesNotContainMatch(regex: Regex) = commonDoesNotContainMatch(regex)
+
+ /**
+ * Returns a [StringSubject]-like instance that will ignore the case of the characters.
+ *
+ * Character equality ignoring case is defined as follows: Characters must be equal either after
+ * calling [Char.lowercaseChar] or after calling [Char.uppercaseChar]. Note that this is
+ * independent of any locale.
+ */
+ actual open fun ignoringCase(): CaseInsensitiveStringComparison = commonIgnoringCase()
+
+ actual inner class CaseInsensitiveStringComparison internal actual constructor() {
+ /**
+ * Fails if the subject is not equal to the given sequence (while ignoring case). For the
+ * purposes of this comparison, two strings are equal if any of the following is true:
+ * * they are equal according to [String.equals] with `ignoreCase = true`
+ * * they are both null
+ *
+ * Example: "abc" is equal to "ABC", but not to "abcd".
+ */
+ actual fun isEqualTo(expected: String?) =
+ commonCaseInsensitiveStringComparisonIsEqualTo(expected)
+
+ /**
+ * Fails if the subject is equal to the given string (while ignoring case). The meaning of
+ * equality is the same as for the [isEqualTo] method.
+ */
+ actual fun isNotEqualTo(unexpected: String?) =
+ commonCaseInsensitiveStringComparisonIsNotEqualTo(unexpected)
+
+ /** Fails if the string does not contain the given sequence (while ignoring case). */
+ actual fun contains(expected: CharSequence?) =
+ commonCaseInsensitiveStringComparisonContains(expected)
+
+ /** Fails if the string contains the given sequence (while ignoring case). */
+ actual fun doesNotContain(expected: CharSequence) =
+ commonCaseInsensitiveStringComparisonDoesNotContain(expected)
+ }
+}
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
index 793f063..de01d95 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
@@ -356,7 +356,12 @@
val request = NavDeepLinkRequest.Builder.fromUri(createRoute(route).toUri()).build()
val matchingDeepLink =
if (this is NavGraph) {
- matchDeepLinkExcludingChildren(request)
+ matchDeepLinkComprehensive(
+ request,
+ searchChildren = false,
+ searchParent = false,
+ lastVisited = this
+ )
} else {
matchDeepLink(request)
}
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
index 4e19649..3a34696 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
@@ -64,24 +64,49 @@
}
}
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public override fun matchDeepLink(navDeepLinkRequest: NavDeepLinkRequest): DeepLinkMatch? {
- // First search through any deep links directly added to this NavGraph
- val bestMatch = super.matchDeepLink(navDeepLinkRequest)
- // Then search through all child destinations for a matching deep link
- val bestChildMatch =
- mapNotNull { child -> child.matchDeepLink(navDeepLinkRequest) }.maxOrNull()
-
- return listOfNotNull(bestMatch, bestChildMatch).maxOrNull()
- }
-
/**
- * Only searches through deep links added directly to this graph. Does not recursively search
- * through its children as [matchDeepLink] does.
+ * Matches deeplink with all children and parents recursively.
+ *
+ * Does not revisit graphs (whether it's a child or parent) if it has already been visited.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public fun matchDeepLinkExcludingChildren(request: NavDeepLinkRequest): DeepLinkMatch? =
- super.matchDeepLink(request)
+ public fun matchDeepLinkComprehensive(
+ navDeepLinkRequest: NavDeepLinkRequest,
+ searchChildren: Boolean,
+ searchParent: Boolean,
+ lastVisited: NavDestination
+ ): DeepLinkMatch? {
+ // First search through any deep links directly added to this NavGraph
+ val bestMatch = super.matchDeepLink(navDeepLinkRequest)
+
+ // If searchChildren is true, search through all child destinations for a matching deeplink
+ val bestChildMatch =
+ if (searchChildren) {
+ mapNotNull { child ->
+ if (child != lastVisited) child.matchDeepLink(navDeepLinkRequest) else null
+ }
+ .maxOrNull()
+ } else null
+
+ // If searchParent is true, search through all parents (and their children) destinations
+ // for a matching deeplink
+ val bestParentMatch =
+ parent?.let {
+ if (searchParent && it != lastVisited)
+ it.matchDeepLinkComprehensive(navDeepLinkRequest, searchChildren, true, this)
+ else null
+ }
+ return listOfNotNull(bestMatch, bestChildMatch, bestParentMatch).maxOrNull()
+ }
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public override fun matchDeepLink(navDeepLinkRequest: NavDeepLinkRequest): DeepLinkMatch? =
+ matchDeepLinkComprehensive(
+ navDeepLinkRequest,
+ searchChildren = true,
+ searchParent = false,
+ lastVisited = this
+ )
/**
* Adds a destination to this NavGraph. The destination must have an [NavDestination.id] id}
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
index b4ae718..325c413 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
@@ -787,6 +787,98 @@
@UiThreadTest
@Test
+ fun testNavigateSharedDestination() {
+ val navController = createNavController()
+ navController.graph =
+ navController.createGraph(route = "root", startDestination = "home_nav") {
+ navigation(route = "home_nav", startDestination = "home") {
+ test("home")
+ test("shared")
+ }
+ navigation(route = "list_nav", startDestination = "list") {
+ test("list")
+ test("shared")
+ }
+ }
+
+ assertThat(navController.currentDestination?.route).isEqualTo("home")
+
+ navController.navigate("shared")
+ val currentDest = navController.currentDestination!!
+ assertThat(currentDest.route).isEqualTo("shared")
+ assertThat(currentDest.parent!!.route).isEqualTo("home_nav")
+ assertThat(navController.currentBackStack.value.map { it.destination.route })
+ .containsExactly("root", "home_nav", "home", "shared")
+ .inOrder()
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateNestedSharedDestination() {
+ val navController = createNavController()
+ navController.graph =
+ navController.createGraph(route = "root", startDestination = "home_nav") {
+ navigation(route = "home_nav", startDestination = "home") {
+ test("home")
+ navigation(route = "home_nested_nav", startDestination = "home_nested") {
+ test("home_nested")
+ test("shared")
+ }
+ }
+ navigation(route = "list_nav", startDestination = "list") {
+ test("list")
+ test("shared")
+ }
+ }
+
+ assertThat(navController.currentDestination?.route).isEqualTo("home")
+
+ navController.navigate("shared")
+ val currentDest = navController.currentDestination!!
+ assertThat(currentDest.route).isEqualTo("shared")
+ assertThat(currentDest.parent!!.route).isEqualTo("home_nested_nav")
+ assertThat(navController.currentBackStack.value.map { it.destination.route })
+ .containsExactly("root", "home_nav", "home", "home_nested_nav", "shared")
+ .inOrder()
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigateParentSharedDestination() {
+ val navController = createNavController()
+ navController.graph =
+ navController.createGraph(route = "root", startDestination = "home_nav") {
+ navigation(route = "home_nav", startDestination = "home") {
+ test("home")
+ test("shared")
+ navigation(route = "home_nested_nav", startDestination = "home_nested") {
+ test("home_nested")
+ }
+ }
+ navigation(route = "list_nav", startDestination = "list") {
+ test("list")
+ test("shared")
+ }
+ }
+
+ assertThat(navController.currentDestination?.route).isEqualTo("home")
+
+ // first navigate into nested graph
+ navController.navigate("home_nested")
+ assertThat(navController.currentDestination!!.route).isEqualTo("home_nested")
+
+ // then navigate to the shared destination that is sibling of parent
+ navController.navigate("shared")
+ val currentDest = navController.currentDestination!!
+ assertThat(currentDest.route).isEqualTo("shared")
+ assertThat(currentDest.parent!!.route).isEqualTo("home_nav")
+ assertThat(navController.currentBackStack.value.map { it.destination.route })
+ .containsExactly("root", "home_nav", "home", "home_nested_nav", "home_nested", "shared")
+ .inOrder()
+ }
+
+ @UiThreadTest
+ @Test
fun testNavigateSingleTopSharedStartDestination() {
val navController = createNavController()
navController.graph =
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
index 1c2703d..9e36bf8 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
@@ -1014,8 +1014,14 @@
// Include the original deep link Intent so the Destinations can
// synthetically generate additional arguments as necessary.
args.putParcelable(KEY_DEEP_LINK_INTENT, activity!!.intent)
+ val currGraph = backQueue.getTopGraph()
val matchingDeepLink =
- _graph!!.matchDeepLink(NavDeepLinkRequest(activity!!.intent))
+ currGraph.matchDeepLinkComprehensive(
+ navDeepLinkRequest = NavDeepLinkRequest(activity!!.intent),
+ searchChildren = true,
+ searchParent = true,
+ lastVisited = currGraph
+ )
if (matchingDeepLink?.matchingArgs != null) {
val destinationArgs =
matchingDeepLink.destination.addInDefaultArgs(
@@ -1403,7 +1409,14 @@
globalArgs.putAll(deepLinkExtras)
}
if (deepLink == null || deepLink.isEmpty()) {
- val matchingDeepLink = _graph!!.matchDeepLink(NavDeepLinkRequest(intent))
+ val currGraph = backQueue.getTopGraph()
+ val matchingDeepLink =
+ currGraph.matchDeepLinkComprehensive(
+ navDeepLinkRequest = NavDeepLinkRequest(intent),
+ searchChildren = true,
+ searchParent = true,
+ lastVisited = currGraph
+ )
if (matchingDeepLink != null) {
val destination = matchingDeepLink.destination
deepLink = destination.buildDeepLinkIds()
@@ -1621,9 +1634,17 @@
if (_graph!!.route == route || _graph!!.matchDeepLink(route) != null) {
return _graph
}
- val currentNode = backQueue.lastOrNull()?.destination ?: _graph!!
- val currentGraph = if (currentNode is NavGraph) currentNode else currentNode.parent!!
- return currentGraph.findNode(route)
+ return backQueue.getTopGraph().findNode(route)
+ }
+
+ /**
+ * Returns the last NavGraph on the backstack.
+ *
+ * If there are no NavGraphs on the stack, returns [_graph]
+ */
+ private fun ArrayDeque<NavBackStackEntry>.getTopGraph(): NavGraph {
+ val currentNode = lastOrNull()?.destination ?: _graph!!
+ return if (currentNode is NavGraph) currentNode else currentNode.parent!!
}
// Finds destination within _graph including its children and
@@ -1886,7 +1907,14 @@
"Cannot navigate to $request. Navigation graph has not been set for " +
"NavController $this."
}
- val deepLinkMatch = _graph!!.matchDeepLink(request)
+ val currGraph = backQueue.getTopGraph()
+ val deepLinkMatch =
+ currGraph.matchDeepLinkComprehensive(
+ navDeepLinkRequest = request,
+ searchChildren = true,
+ searchParent = true,
+ lastVisited = currGraph
+ )
if (deepLinkMatch != null) {
val destination = deepLinkMatch.destination
val args = destination.addInDefaultArgs(deepLinkMatch.matchingArgs) ?: Bundle()
diff --git a/paging/integration-tests/testapp/build.gradle b/paging/integration-tests/testapp/build.gradle
index cbd8d34..a1918e9 100644
--- a/paging/integration-tests/testapp/build.gradle
+++ b/paging/integration-tests/testapp/build.gradle
@@ -41,12 +41,10 @@
// Only needed to ensure version of annotation:annotation matches in impl
// and androidTestImpl, for both AOSP and playground builds.
- implementation(project(":annotation:annotation"))
- implementation(project(":annotation:annotation-experimental"))
+ implementation("androidx.annotation:annotation:1.8.1")
+ implementation("androidx.annotation:annotation-experimental:1.4.1")
// Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
- androidTestImplementation(project(":annotation:annotation"))
- androidTestImplementation(project(":annotation:annotation-experimental"))
androidTestImplementation(libs.kotlinTest)
androidTestImplementation(libs.testCore)
androidTestImplementation(libs.testExtJunit)
diff --git a/pdf/integration-tests/testapp/build.gradle b/pdf/integration-tests/testapp/build.gradle
index d8990dc..24a850b 100644
--- a/pdf/integration-tests/testapp/build.gradle
+++ b/pdf/integration-tests/testapp/build.gradle
@@ -6,7 +6,6 @@
android {
namespace 'androidx.pdf.testapp'
- buildToolsVersion "35.0.0-rc1"
defaultConfig {
applicationId "androidx.pdf.testapp"
@@ -27,7 +26,6 @@
implementation(libs.testCore)
androidTestImplementation(libs.testExtJunit)
- androidTestImplementation(libs.junit)
androidTestImplementation(libs.testCore)
androidTestImplementation(libs.testRules)
androidTestImplementation(libs.testRunner)
diff --git a/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/testapp/MainActivityTest.kt b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/testapp/MainActivityTest.kt
new file mode 100644
index 0000000..f59bcb5
--- /dev/null
+++ b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/testapp/MainActivityTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.pdf.testapp
+
+import androidx.activity.result.ActivityResultLauncher
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ActivityScenario.launch
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.android.material.button.MaterialButton
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.*
+
+@Suppress("UNCHECKED_CAST")
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MainActivityTest {
+ @get:Rule var activityScenarioRule = ActivityScenarioRule(MainActivity::class.java)
+
+ @Test
+ fun testActivityInitialization() {
+ launch(MainActivity::class.java).use { scenario ->
+ scenario.moveToState(Lifecycle.State.CREATED)
+
+ Assert.assertEquals(Lifecycle.State.CREATED, scenario.state)
+ }
+ }
+
+ @Test
+ fun testGetContentButtonClickLaunchesFilePicker() {
+ val mockFilePicker: ActivityResultLauncher<String> =
+ mock(ActivityResultLauncher::class.java) as ActivityResultLauncher<String>
+
+ launch(MainActivity::class.java).use { scenario ->
+ scenario.moveToState(Lifecycle.State.CREATED)
+ scenario.onActivity { activity ->
+ activity.filePicker = mockFilePicker
+ val getContentButton: MaterialButton = activity.findViewById(R.id.launch_button)
+
+ Assert.assertNotNull(getContentButton)
+ getContentButton.performClick()
+ verify(mockFilePicker).launch("application/pdf")
+ }
+ }
+ }
+}
diff --git a/pdf/integration-tests/testapp/src/main/AndroidManifest.xml b/pdf/integration-tests/testapp/src/main/AndroidManifest.xml
index 2c55785..68c94d8 100644
--- a/pdf/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/pdf/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -24,7 +24,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
- android:theme="@style/Theme.Androidx"
+ android:theme="@style/AppTheme"
tools:replace="android:label">
<activity
android:name=".MainActivity"
diff --git a/pdf/integration-tests/testapp/src/main/kotlin/androidx/pdf/testapp/MainActivity.kt b/pdf/integration-tests/testapp/src/main/kotlin/androidx/pdf/testapp/MainActivity.kt
index ef1d51b..d4d0f6c 100644
--- a/pdf/integration-tests/testapp/src/main/kotlin/androidx/pdf/testapp/MainActivity.kt
+++ b/pdf/integration-tests/testapp/src/main/kotlin/androidx/pdf/testapp/MainActivity.kt
@@ -19,8 +19,10 @@
import android.annotation.SuppressLint
import android.net.Uri
import android.os.Bundle
+import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.GetContent
import androidx.annotation.RestrictTo
+import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
@@ -32,9 +34,15 @@
class MainActivity : AppCompatActivity() {
private var pdfViewerFragment: PdfViewerFragment? = null
+ private var isPdfViewInitialized = false
- private val filePicker =
+ @VisibleForTesting
+ var filePicker: ActivityResultLauncher<String> =
registerForActivityResult(GetContent()) { uri: Uri? ->
+ if (!isPdfViewInitialized) {
+ setPdfView()
+ isPdfViewInitialized = true
+ }
uri?.let { pdfViewerFragment?.documentUri = uri }
}
@@ -53,9 +61,6 @@
getContentButton.setOnClickListener { filePicker.launch(MIME_TYPE_PDF) }
searchButton.setOnClickListener { setFindInFileViewVisible() }
- if (savedInstanceState == null) {
- setPdfView()
- }
}
private fun setPdfView() {
diff --git a/pdf/integration-tests/testapp/src/main/res/layout/activity_main.xml b/pdf/integration-tests/testapp/src/main/res/layout/activity_main.xml
index 76f5b6cf..016b59a 100644
--- a/pdf/integration-tests/testapp/src/main/res/layout/activity_main.xml
+++ b/pdf/integration-tests/testapp/src/main/res/layout/activity_main.xml
@@ -15,8 +15,10 @@
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/pdf_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
@@ -25,30 +27,42 @@
<FrameLayout
android:id="@+id/fragment_container_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1" />
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginBottom="8dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/launch_button"/>
- <LinearLayout
- android:layout_width="match_parent"
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/launch_button"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="horizontal">
+ android:text="@string/launch_string"
+ android:textColor="@color/google_white"
+ app:backgroundTint="@color/google_grey"
+ app:strokeColor="@color/google_white"
+ app:strokeWidth="1dp"
+ android:layout_marginEnd="8dp"
+ android:layout_marginBottom="16dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/search_button"/>
- <com.google.android.material.button.MaterialButton
- android:id="@+id/launch_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:text="@string/launch_string"
- android:textSize="16sp" />
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/search_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/search_string"
+ android:textColor="@color/google_white"
+ app:backgroundTint="@color/google_grey"
+ app:strokeColor="@color/google_white"
+ app:strokeWidth="1dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginBottom="16dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@id/launch_button"
+ app:layout_constraintEnd_toEndOf="parent" />
- <com.google.android.material.button.MaterialButton
- android:id="@+id/search_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:text="@string/search_string"
- android:textSize="16sp" />
- </LinearLayout>
-
-</LinearLayout>
\ No newline at end of file
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/pdf/integration-tests/testapp/src/main/res/values-night/strings.xml b/pdf/integration-tests/testapp/src/main/res/values-night/strings.xml
index 5b89e97..f1e321a 100644
--- a/pdf/integration-tests/testapp/src/main/res/values-night/strings.xml
+++ b/pdf/integration-tests/testapp/src/main/res/values-night/strings.xml
@@ -15,5 +15,5 @@
-->
<resources>
- <string name="launch_string">Launch AOSP PDF Viewer</string>
+ <string name="launch_string">Open Pdf</string>
</resources>
\ No newline at end of file
diff --git a/pdf/integration-tests/testapp/src/main/res/values/strings.xml b/pdf/integration-tests/testapp/src/main/res/values/strings.xml
index ee8e986..60a4880 100644
--- a/pdf/integration-tests/testapp/src/main/res/values/strings.xml
+++ b/pdf/integration-tests/testapp/src/main/res/values/strings.xml
@@ -1,5 +1,5 @@
<resources>
- <string name="app_name">PDF Viewer Integration Tests</string>
- <string name="launch_string">Launch AOSP PDF Viewer</string>
+ <string name="app_name">PDF Viewer Sample App</string>
+ <string name="launch_string">Open Pdf</string>
<string name="search_string">Search</string>
</resources>
\ No newline at end of file
diff --git a/pdf/integration-tests/testapp/src/main/res/values/themes.xml b/pdf/integration-tests/testapp/src/main/res/values/themes.xml
index ffe1464..b12dd9b 100644
--- a/pdf/integration-tests/testapp/src/main/res/values/themes.xml
+++ b/pdf/integration-tests/testapp/src/main/res/values/themes.xml
@@ -1,7 +1,10 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
- <style name="Base.Theme.Androidx" parent="Theme.Material3.DayNight.NoActionBar">
+ <style name="BaseAppTheme" parent="Theme.Material3.Dark.NoActionBar">
+ <item name="colorPrimary">@color/google_grey</item>
+ <item name="colorPrimaryDark">@color/black</item>
+ <item name="colorAccent">@color/google_white</item>
</style>
- <style name="Theme.Androidx" parent="Base.Theme.Androidx" />
+ <style name="AppTheme" parent="BaseAppTheme" />
</resources>
\ No newline at end of file
diff --git a/pdf/pdf-viewer-fragment/build.gradle b/pdf/pdf-viewer-fragment/build.gradle
index 62e1c47..adebc12 100644
--- a/pdf/pdf-viewer-fragment/build.gradle
+++ b/pdf/pdf-viewer-fragment/build.gradle
@@ -40,7 +40,6 @@
defaultConfig {
minSdk 31
- buildToolsVersion "35.0.0-rc1"
compileSdk 35
}
}
diff --git a/pdf/pdf-viewer/build.gradle b/pdf/pdf-viewer/build.gradle
index 45aca39..faa169f 100644
--- a/pdf/pdf-viewer/build.gradle
+++ b/pdf/pdf-viewer/build.gradle
@@ -24,7 +24,6 @@
}
dependencies {
- api(libs.rxjava2)
api(libs.guavaAndroid)
api(libs.kotlinCoroutinesCore)
api("androidx.fragment:fragment-ktx:1.8.1")
@@ -64,7 +63,6 @@
defaultConfig {
minSdk 31
- buildToolsVersion "35.0.0-rc1"
compileSdk 35
}
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java
index 77ca69e..405014c 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java
@@ -84,6 +84,7 @@
@Override
protected void onDragHandleMove(int deltaX, int deltaY) {
+ mSelectionActionMode.stopActionMode();
SelectionBoundary updated = SelectionBoundary.atPoint(mDragging.getX() + deltaX,
mDragging.getY() + deltaY);
mSelectionModel.updateSelectionAsync(mFixed, updated);
@@ -92,6 +93,5 @@
@Override
protected void onDragHandleUp() {
mSelectionActionMode.resume();
- // Nothing required.
}
}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/util/KspTestRunner.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/util/KspTestRunner.kt
index 977f26e..101939d 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/util/KspTestRunner.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/util/KspTestRunner.kt
@@ -20,6 +20,7 @@
import androidx.privacysandbox.tools.core.model.ParsedApi
import androidx.privacysandbox.tools.testing.CompilationResultSubject
import androidx.privacysandbox.tools.testing.CompilationTestHelper.assertThat
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.compiler.TestCompilationArguments
import androidx.room.compiler.processing.util.compiler.compile
@@ -40,6 +41,7 @@
TestCompilationArguments(
sources = sources.toList(),
symbolProcessorProviders = listOf(provider),
+ kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
)
)
)
@@ -55,8 +57,9 @@
Files.createTempDirectory("test").toFile(),
TestCompilationArguments(
sources = sources.asList(),
- symbolProcessorProviders = listOf(provider)
- )
+ symbolProcessorProviders = listOf(provider),
+ kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
+ ),
)
return assertThat(result).also { it.fails() }
}
diff --git a/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt b/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
index 51bcaed..7c68d5b 100644
--- a/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
+++ b/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
@@ -18,6 +18,7 @@
import androidx.room.compiler.processing.util.DiagnosticLocation
import androidx.room.compiler.processing.util.DiagnosticMessage
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.compiler.TestCompilationArguments
import androidx.room.compiler.processing.util.compiler.TestCompilationResult
@@ -50,6 +51,7 @@
classpath = extraClasspath,
symbolProcessorProviders = symbolProcessorProviders,
processorOptions = processorOptions,
+ kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
)
)
}
diff --git a/remotecallback/remotecallback/build.gradle b/remotecallback/remotecallback/build.gradle
index e5ff2a7..c1da69a 100644
--- a/remotecallback/remotecallback/build.gradle
+++ b/remotecallback/remotecallback/build.gradle
@@ -30,14 +30,14 @@
dependencies {
api("androidx.annotation:annotation:1.8.1")
- implementation(project(":collection:collection"))
+ implementation("androidx.collection:collection:1.4.2")
androidTestImplementation(libs.testExtJunit)
androidTestImplementation(libs.testCore)
androidTestImplementation(libs.testRunner)
androidTestImplementation(libs.mockitoCore, excludes.bytebuddy)
androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy)
- androidTestAnnotationProcessor (project(":remotecallback:remotecallback-processor"))
+ androidTestAnnotationProcessor(project(":remotecallback:remotecallback-processor"))
}
android {
diff --git a/resourceinspection/resourceinspection-processor/build.gradle b/resourceinspection/resourceinspection-processor/build.gradle
index 720cf10e..5251358 100644
--- a/resourceinspection/resourceinspection-processor/build.gradle
+++ b/resourceinspection/resourceinspection-processor/build.gradle
@@ -46,7 +46,7 @@
testImplementation(libs.truth)
testRuntimeOnly(project(":resourceinspection:resourceinspection-annotation"))
- testRuntimeOnly(project(":annotation:annotation"))
+ testRuntimeOnly("androidx.annotation:annotation:1.8.1")
testRuntimeOnly(SdkHelperKt.getSdkDependency(project))
testRuntimeOnly(libs.intellijAnnotations)
testRuntimeOnly(libs.jsr250)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt
index b576bad..cf236fe 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt
@@ -35,7 +35,6 @@
import kotlinx.coroutines.test.TestScope
import org.junit.After
import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -242,8 +241,9 @@
private fun assertTransactionQueries() {
testCoroutineScope.testScheduler.advanceUntilIdle()
- assertNotNull(queryAndArgs.any { it.equals("BEGIN TRANSACTION") })
- assertNotNull(queryAndArgs.any { it.equals("TRANSACTION SUCCESSFUL") })
- assertNotNull(queryAndArgs.any { it.equals("END TRANSACTION") })
+ val queries = queryAndArgs.map { it.first }
+ assertThat(queries).contains("BEGIN IMMEDIATE TRANSACTION")
+ assertThat(queries).contains("TRANSACTION SUCCESSFUL")
+ assertThat(queries).contains("END TRANSACTION")
}
}
diff --git a/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/JvmOnlyDatabaseDeclarationTest.kt b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/JvmOnlyDatabaseDeclarationTest.kt
new file mode 100644
index 0000000..d716001
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/JvmOnlyDatabaseDeclarationTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.multiplatformtestapp.test
+
+import androidx.kruth.assertThat
+import androidx.room.Database
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.useReaderConnection
+import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+
+/**
+ * This test validates that a [Database] declared in the Jvm source set does not required the usage
+ * of [androidx.room.ConstructedBy] nor [androidx.room.RoomDatabaseConstructor].
+ */
+class JvmOnlyDatabaseDeclarationTest {
+
+ @Test
+ fun buildJvmOnlyRoomDatabase() = runTest {
+ val database =
+ Room.inMemoryDatabaseBuilder<TestDatabase>().setDriver(BundledSQLiteDriver()).build()
+ val dbVersion =
+ database.useReaderConnection { connection ->
+ connection.usePrepared("PRAGMA user_version") {
+ it.step()
+ it.getLong(0)
+ }
+ }
+ assertThat(dbVersion).isEqualTo(1)
+ }
+
+ @Database(entities = [TestEntity::class], version = 1, exportSchema = false)
+ abstract class TestDatabase : RoomDatabase()
+
+ @Entity data class TestEntity(@PrimaryKey val id: Long)
+}
diff --git a/room/room-compiler-processing-testing/build.gradle b/room/room-compiler-processing-testing/build.gradle
index f3b5539..77d66f7 100644
--- a/room/room-compiler-processing-testing/build.gradle
+++ b/room/room-compiler-processing-testing/build.gradle
@@ -30,18 +30,26 @@
id("kotlin")
}
+ext {
+ kotlin = "2.0.20-Beta2"
+ ksp = "${kotlin}-1.0.23"
+}
+
dependencies {
api(project(":room:room-compiler-processing"))
- implementation(libs.kotlinStdlib)
- implementation(libs.kspApi)
- implementation(libs.kotlinStdlibJdk8) // KSP defines older version as dependency, force update.
- implementation(libs.ksp)
+ implementation(libs.kotlinStdlibJdk8)
+ // For Java source compilation
implementation(libs.googleCompileTesting)
- // specify these to match the kotlin compiler version in AndroidX rather than what KSP or KCT
- // uses
- implementation(libs.kotlinCompilerEmbeddable)
- implementation(libs.kotlinDaemonEmbeddable)
- implementation(libs.kotlinAnnotationProcessingEmbeddable)
+ // TODO(b/314151707): Use the versions in Androidx once Androidx moves to 2.x.
+ // For KSP processing
+ implementation("com.google.devtools.ksp:symbol-processing:${ksp}")
+ implementation("com.google.devtools.ksp:symbol-processing-api:${ksp}")
+ implementation("com.google.devtools.ksp:symbol-processing-common-deps:${ksp}")
+ implementation("com.google.devtools.ksp:symbol-processing-aa-embeddable:${ksp}")
+ // For Kotlin source compilation and KAPT
+ implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlin}")
+ implementation("org.jetbrains.kotlin:kotlin-daemon-embeddable:${kotlin}")
+ implementation("org.jetbrains.kotlin:kotlin-annotation-processing-embeddable:${kotlin}")
}
/**
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
index 8a862b0..afbc127 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
@@ -68,7 +68,7 @@
override fun toString(): String {
return buildString {
appendLine("CompilationResult (with $testRunnerName)")
- Diagnostic.Kind.entries.forEach { kind ->
+ Diagnostic.Kind.values().forEach { kind ->
val messages = diagnosticsOfKind(kind)
appendLine("${kind.name}: ${messages.size}")
messages.forEach { appendLine(it) }
@@ -98,7 +98,10 @@
"Scripting plugin will not be loaded: not",
"Using JVM IR backend",
"Configuring the compilation environment",
- "Loading modules:"
+ "Loading modules:",
+ // TODO: Remove once we use a Kotlin 2.x version that has
+ // https://github.com/JetBrains/kotlin/commit/7e9d6e601d007bc1250e1b47c6b2e55e3399145b
+ "Kapt currently doesn't support language version"
)
}
}
@@ -474,7 +477,7 @@
@ExperimentalProcessingApi
internal class JavaCompileTestingCompilationResult(
testRunner: CompilationTestRunner,
- private val delegate: Compilation,
+ @Suppress("unused") private val delegate: Compilation,
processor: SyntheticJavacProcessor,
diagnostics: Map<Diagnostic.Kind, List<DiagnosticMessage>>,
override val generatedSources: List<Source>,
@@ -494,7 +497,8 @@
}
@ExperimentalProcessingApi
-internal class KotlinCompilationResult(
+internal class KotlinCompilationResult
+constructor(
testRunner: CompilationTestRunner,
processor: SyntheticProcessor,
private val delegate: TestCompilationResult
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
index d0065be1..b932e31 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
@@ -549,3 +549,6 @@
tmpDir.deleteRecursively()
}
}
+
+/** Kotlin compiler arguments for K1 */
+val KOTLINC_LANGUAGE_1_9_ARGS = listOf("-language-version=1.9", "-api-version=1.9")
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/DelegatingTestRegistrar.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/DelegatingTestRegistrar.kt
index bea0abc..e62003f 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/DelegatingTestRegistrar.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/DelegatingTestRegistrar.kt
@@ -16,98 +16,151 @@
package androidx.room.compiler.processing.util.compiler
-import androidx.room.compiler.processing.util.compiler.DelegatingTestRegistrar.Companion.runCompilation
+import androidx.room.compiler.processing.util.compiler.DelegatingTestRegistrar.runCompilation
import java.net.URI
-import java.nio.file.Paths
+import kotlin.io.path.absolute
+import kotlin.io.path.toPath
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.com.intellij.mock.MockProject
+import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.util.ServiceLoaderLite
/**
- * A component registrar for Kotlin Compiler that delegates to a list of thread local delegates.
+ * A utility object for setting up Kotlin Compiler plugins that delegate to a list of thread local
+ * plugins.
*
* see [runCompilation] for usages.
*/
-@Suppress("DEPRECATION") // TODO: Migrate ComponentRegistrar to CompilerPluginRegistrar
@OptIn(ExperimentalCompilerApi::class)
-internal class DelegatingTestRegistrar :
- @Suppress("DEPRECATION") org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar {
- override fun registerProjectComponents(
- project: MockProject,
- configuration: CompilerConfiguration
- ) {
- delegates.get()?.let { it.forEach { it.registerProjectComponents(project, configuration) } }
+object DelegatingTestRegistrar {
+
+ @Suppress("DEPRECATION")
+ private val k1Delegates =
+ ThreadLocal<List<org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar>>()
+
+ private val k2Delegates = ThreadLocal<List<CompilerPluginRegistrar>>()
+
+ class K1Registrar :
+ @Suppress("DEPRECATION") org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar {
+ override fun registerProjectComponents(
+ project: MockProject,
+ configuration: CompilerConfiguration
+ ) {
+ k1Delegates.get()?.forEach { it.registerProjectComponents(project, configuration) }
+ }
+
+ // FirKotlinToJvmBytecodeCompiler throws an error when it sees an incompatible plugin.
+ override val supportsK2: Boolean
+ get() = true
}
- companion object {
- private const val REGISTRAR_CLASSPATH =
- "META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar"
+ class K2Registrar : CompilerPluginRegistrar() {
+ override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
+ k2Delegates.get()?.forEach { with(it) { registerExtensions(configuration) } }
+ }
- private val resourcePathForSelfClassLoader by lazy {
+ override val supportsK2: Boolean
+ get() = true
+ }
+
+ private const val K1_SERVICES_REGISTRAR_PATH =
+ "META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar"
+
+ private const val K2_SERVICES_REGISTRAR_PATH =
+ "META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar"
+
+ private val k1ResourcePathForSelfClassLoader by lazy {
+ getResourcePathForClassLoader(K1_SERVICES_REGISTRAR_PATH)
+ }
+
+ private val k2ResourcePathForSelfClassLoader by lazy {
+ getResourcePathForClassLoader(K2_SERVICES_REGISTRAR_PATH)
+ }
+
+ private fun getResourcePathForClassLoader(servicesRegistrarPath: String): String {
+ val registrarClassToLoad =
+ when (servicesRegistrarPath) {
+ K1_SERVICES_REGISTRAR_PATH ->
+ @Suppress("DEPRECATION")
+ org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar::class
+ K2_SERVICES_REGISTRAR_PATH -> CompilerPluginRegistrar::class
+ else -> error("Unknown services registrar path: $servicesRegistrarPath")
+ }
+ val expectedRegistrarClass =
+ when (servicesRegistrarPath) {
+ K1_SERVICES_REGISTRAR_PATH -> K1Registrar::class
+ K2_SERVICES_REGISTRAR_PATH -> K2Registrar::class
+ else -> error("Unknown services registrar path: $servicesRegistrarPath")
+ }
+ val classpath =
this::class
.java
.classLoader
- .getResources(REGISTRAR_CLASSPATH)
+ .getResources(servicesRegistrarPath)
.asSequence()
.mapNotNull { url ->
- val uri = URI.create(url.toString().removeSuffix("/$REGISTRAR_CLASSPATH"))
+ val uri = URI.create(url.toString().removeSuffix("/$servicesRegistrarPath"))
when (uri.scheme) {
- "jar" -> Paths.get(URI.create(uri.schemeSpecificPart.removeSuffix("!")))
- "file" -> Paths.get(uri)
+ "jar" -> URI.create(uri.schemeSpecificPart.removeSuffix("!")).toPath()
+ "file" -> uri.toPath()
else -> return@mapNotNull null
- }.toAbsolutePath()
+ }.absolute()
}
.find { resourcesPath ->
ServiceLoaderLite.findImplementations(
- @Suppress("DEPRECATION")
- org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar::class.java,
+ registrarClassToLoad.java,
listOf(resourcesPath.toFile())
)
.any { implementation ->
- implementation == DelegatingTestRegistrar::class.java.name
+ implementation == expectedRegistrarClass.java.name
}
}
- ?.toString()
- ?: throw AssertionError(
- """
- Could not find the ComponentRegistrar class loader that should load
- ${DelegatingTestRegistrar::class.qualifiedName}
- """
- .trimIndent()
- )
+ if (classpath == null) {
+ throw AssertionError(
+ """
+ Could not find the $registrarClassToLoad class loader that should load
+ $expectedRegistrarClass
+ """
+ .trimIndent()
+ )
}
- @Suppress("DEPRECATION")
- private val delegates =
- ThreadLocal<List<org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar>>()
+ return classpath.toString()
+ }
- fun runCompilation(
- compiler: K2JVMCompiler,
- messageCollector: MessageCollector,
- arguments: K2JVMCompilerArguments,
- @Suppress("DEPRECATION")
- pluginRegistrars: List<org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar>
- ): ExitCode {
- try {
- arguments.addDelegatingTestRegistrar()
- delegates.set(pluginRegistrars)
- return compiler.exec(
- messageCollector = messageCollector,
- services = Services.EMPTY,
- arguments = arguments
- )
- } finally {
- delegates.remove()
- }
+ internal fun runCompilation(
+ compiler: K2JVMCompiler,
+ messageCollector: MessageCollector,
+ arguments: K2JVMCompilerArguments,
+ registrars: PluginRegistrarArguments
+ ): ExitCode {
+ try {
+ k1Delegates.set(registrars.k1Registrars)
+ k2Delegates.set(registrars.k2Registrars)
+ arguments.addDelegatingTestRegistrars()
+ return compiler.exec(
+ messageCollector = messageCollector,
+ services = Services.EMPTY,
+ arguments = arguments
+ )
+ } finally {
+ k1Delegates.remove()
+ k2Delegates.remove()
}
+ }
- private fun K2JVMCompilerArguments.addDelegatingTestRegistrar() {
- pluginClasspaths = (pluginClasspaths ?: arrayOf()) + resourcePathForSelfClassLoader
- }
+ private fun K2JVMCompilerArguments.addDelegatingTestRegistrars() {
+ pluginClasspaths =
+ buildList {
+ pluginClasspaths?.let { addAll(it) }
+ add(k1ResourcePathForSelfClassLoader)
+ add(k2ResourcePathForSelfClassLoader)
+ }
+ .toTypedArray()
}
}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/KotlinCliRunner.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/KotlinCliRunner.kt
index 7515bac..5335ea3 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/KotlinCliRunner.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/KotlinCliRunner.kt
@@ -18,66 +18,35 @@
import androidx.room.compiler.processing.util.compiler.steps.CompilationStepArguments
import androidx.room.compiler.processing.util.compiler.steps.RawDiagnosticMessage
-import androidx.room.compiler.processing.util.getSystemClasspaths
import java.io.File
-import java.net.URLClassLoader
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
-import org.jetbrains.kotlin.config.JvmDefaultMode
-import org.jetbrains.kotlin.config.JvmTarget
+import org.jetbrains.kotlin.compiler.plugin.parseLegacyPluginOption
+import org.jetbrains.kotlin.config.LanguageVersion
/** Utility object to run kotlin compiler via its CLI API. */
internal object KotlinCliRunner {
private val compiler = K2JVMCompiler()
- private fun List<SourceSet>.existingRootPaths() =
- this.asSequence().map { it.root }.filter { it.exists() }.map { it.canonicalPath }.distinct()
-
- private fun CompilationStepArguments.copyToCliArguments(cliArguments: K2JVMCompilerArguments) {
- // stdlib is in the classpath so no need to specify it here.
- cliArguments.noStdlib = true
- cliArguments.noReflect = true
- cliArguments.jvmTarget = JvmTarget.JVM_1_8.description
- cliArguments.noOptimize = true
- // useJavac & compileJava are experimental so lets not use it for now.
- cliArguments.useJavac = false
- cliArguments.compileJava = false
- cliArguments.jvmDefault = JvmDefaultMode.ALL_COMPATIBILITY.description
- cliArguments.allowNoSourceFiles = true
- cliArguments.javacArguments = javacArguments.toTypedArray()
- val inherited =
- if (inheritClasspaths) {
- inheritedClasspath
- } else {
- emptyList()
- }
- cliArguments.classpath =
- (additionalClasspaths + inherited)
- .filter { it.exists() }
- .distinct()
- .joinToString(separator = File.pathSeparator) { it.canonicalPath }
- cliArguments.javaSourceRoots =
- this.sourceSets.filter { it.hasJavaSource }.existingRootPaths().toList().toTypedArray()
- cliArguments.freeArgs += this.sourceSets.filter { it.hasKotlinSource }.existingRootPaths()
- }
-
- /** Runs the kotlin cli API with the given arguments. */
- @OptIn(ExperimentalCompilerApi::class)
+ /** Runs the Kotlin CLI API with the given arguments. */
fun runKotlinCli(
/** Compilation arguments (sources, classpaths etc) */
arguments: CompilationStepArguments,
/** Destination directory where generated class files will be written to */
destinationDir: File,
- /** List of component registrars for the compilation. */
- @Suppress("DEPRECATION")
- pluginRegistrars: List<org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar>
+ /** List of plugin registrars for the compilation. */
+ @OptIn(ExperimentalCompilerApi::class)
+ pluginRegistrars: PluginRegistrarArguments =
+ PluginRegistrarArguments(emptyList(), emptyList())
): KotlinCliResult {
- val cliArguments = compiler.createArguments()
destinationDir.mkdirs()
- cliArguments.destination = destinationDir.absolutePath
- arguments.copyToCliArguments(cliArguments)
+ val cliArguments =
+ compiler.createArguments().apply {
+ destination = destinationDir.absolutePath
+ arguments.copyToCliArguments(this)
+ }
compiler.parseArguments(arguments.kotlincArguments.toTypedArray(), cliArguments)
val diagnosticsMessageCollector = DiagnosticsMessageCollector("kotlinc")
@@ -86,7 +55,7 @@
compiler = compiler,
messageCollector = diagnosticsMessageCollector,
arguments = cliArguments,
- pluginRegistrars = pluginRegistrars
+ registrars = pluginRegistrars
)
return KotlinCliResult(
@@ -97,6 +66,53 @@
)
}
+ /** Get the language version specified with `-language-version=xxx`. */
+ fun getLanguageVersion(kotlincArguments: List<String>): LanguageVersion {
+ val cliArguments = compiler.createArguments()
+ compiler.parseArguments(kotlincArguments.toTypedArray(), cliArguments)
+ return cliArguments.languageVersion?.let { LanguageVersion.fromVersionString(it) }
+ ?: TestDefaultOptions.kotlinLanguageVersion
+ }
+
+ private fun CompilationStepArguments.copyToCliArguments(cliArguments: K2JVMCompilerArguments) {
+ // stdlib is in the classpath so no need to specify it here.
+ cliArguments.noStdlib = true
+ cliArguments.noReflect = true
+ cliArguments.noOptimize = true
+
+ // We want allow no sources to run test handlers
+ cliArguments.allowNoSourceFiles = true
+
+ cliArguments.languageVersion = TestDefaultOptions.kotlinLanguageVersion.versionString
+ cliArguments.apiVersion = TestDefaultOptions.kotlinApiVersion.versionString
+ cliArguments.jvmTarget = TestDefaultOptions.jvmTarget.description
+ cliArguments.jvmDefault = TestDefaultOptions.jvmDefaultMode.description
+
+ // useJavac & compileJava are experimental so lets not use it for now.
+ cliArguments.useJavac = false
+ cliArguments.compileJava = false
+
+ cliArguments.javacArguments = javacArguments.toTypedArray()
+
+ val inherited =
+ if (inheritClasspaths) {
+ TestClasspath.inheritedClasspath
+ } else {
+ emptyList()
+ }
+ cliArguments.classpath =
+ (additionalClasspaths + inherited)
+ .filter { it.exists() }
+ .distinct()
+ .joinToString(separator = File.pathSeparator) { it.canonicalPath }
+
+ cliArguments.javaSourceRoots =
+ this.sourceSets.filter { it.hasJavaSource }.existingRootPaths().toList().toTypedArray()
+
+ // Sources to compile are passed as args
+ cliArguments.freeArgs += this.sourceSets.filter { it.hasKotlinSource }.existingRootPaths()
+ }
+
/** Result of a kotlin compilation request */
internal class KotlinCliResult(
/** The exit code reported by the compiler */
@@ -109,47 +125,18 @@
val kotlinCliArguments: K2JVMCompilerArguments
)
- private val inheritedClasspath by
- lazy(LazyThreadSafetyMode.NONE) {
- getClasspathFromClassloader(KotlinCliRunner::class.java.classLoader)
- }
-
- // ported from https://github.com/google/compile-testing/blob/master/src/main/java/com
- // /google/testing/compile/Compiler.java#L231
- private fun getClasspathFromClassloader(referenceClassLoader: ClassLoader): List<File> {
- val platformClassLoader: ClassLoader = ClassLoader.getPlatformClassLoader()
- var currentClassloader = referenceClassLoader
- val systemClassLoader = ClassLoader.getSystemClassLoader()
-
- // Concatenate search paths from all classloaders in the hierarchy
- // 'till the system classloader.
- val classpaths: MutableSet<String> = LinkedHashSet()
- while (true) {
- if (currentClassloader === systemClassLoader) {
- classpaths.addAll(getSystemClasspaths())
- break
- }
- if (currentClassloader === platformClassLoader) {
- break
- }
- check(currentClassloader is URLClassLoader) {
- """Classpath for compilation could not be extracted
- since $currentClassloader is not an instance of URLClassloader
- """
- .trimIndent()
- }
- // We only know how to extract classpaths from URLClassloaders.
- currentClassloader.urLs.forEach { url ->
- check(url.protocol == "file") {
- """Given classloader consists of classpaths which are unsupported for
- compilation.
- """
- .trimIndent()
- }
- classpaths.add(url.path)
- }
- currentClassloader = currentClassloader.parent
- }
- return classpaths.map { File(it) }.filter { it.exists() }
+ internal fun getPluginOptions(
+ pluginId: String,
+ kotlincArguments: List<String>
+ ): Map<String, String> {
+ val options =
+ kotlincArguments
+ .dropLast(1)
+ .zip(kotlincArguments.drop(1))
+ .filter { it.first == "-P" }
+ .mapNotNull { parseLegacyPluginOption(it.second) }
+ return options
+ .filter { it.pluginId == pluginId }
+ .associateBy({ it.optionName }, { it.value })
}
}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/Ksp1Compilation.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/Ksp1Compilation.kt
new file mode 100644
index 0000000..92cdd9d
--- /dev/null
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/Ksp1Compilation.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.util.compiler
+
+import androidx.room.compiler.processing.util.FileResource
+import androidx.room.compiler.processing.util.compiler.steps.CompilationStepArguments
+import androidx.room.compiler.processing.util.compiler.steps.CompilationStepResult
+import androidx.room.compiler.processing.util.compiler.steps.resolveDiagnostics
+import com.google.devtools.ksp.KspOptions
+import com.google.devtools.ksp.processing.SymbolProcessorProvider
+import java.io.File
+import org.jetbrains.kotlin.cli.common.ExitCode
+import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
+
+internal class Ksp1Compilation(
+ private val name: String,
+ private val symbolProcessorProviders: List<SymbolProcessorProvider>,
+ private val processorOptions: Map<String, String>
+) {
+ private fun createKspOptions(workingDir: File): KspOptions.Builder {
+ return KspOptions.Builder().apply {
+ this.javaOutputDir = workingDir.resolve(JAVA_OUT_DIR)
+ this.kotlinOutputDir = workingDir.resolve(KOTLIN_OUT_DIR)
+ this.processingOptions.putAll(processorOptions)
+ }
+ }
+
+ @OptIn(ExperimentalCompilerApi::class)
+ fun execute(workingDir: File, arguments: CompilationStepArguments): CompilationStepResult {
+ if (symbolProcessorProviders.isEmpty()) {
+ return CompilationStepResult.skip(arguments)
+ }
+ val kspMessages = DiagnosticsMessageCollector(name)
+ val result =
+ KotlinCliRunner.runKotlinCli(
+ arguments = arguments,
+ destinationDir = workingDir.resolve(CLASS_OUT_FOLDER_NAME),
+ pluginRegistrars =
+ PluginRegistrarArguments(
+ listOf(
+ TestKspRegistrar(
+ kspWorkingDir = workingDir.resolve("ksp-compiler"),
+ baseOptions = createKspOptions(workingDir),
+ processorProviders = symbolProcessorProviders,
+ messageCollector = kspMessages
+ )
+ ),
+ emptyList()
+ ),
+ )
+ // workaround for https://github.com/google/ksp/issues/623
+ val failureDueToWarnings =
+ result.kotlinCliArguments.allWarningsAsErrors && kspMessages.hasWarnings()
+
+ val generatedSources =
+ listOfNotNull(
+ workingDir.resolve(KOTLIN_OUT_DIR).toSourceSet(),
+ workingDir.resolve(JAVA_OUT_DIR).toSourceSet(),
+ )
+ val diagnostics =
+ resolveDiagnostics(
+ diagnostics = result.diagnostics + kspMessages.getDiagnostics(),
+ sourceSets = arguments.sourceSets + generatedSources
+ )
+ val outputResources = workingDir.resolve(RESOURCES_OUT_FOLDER_NAME)
+ val outputClasspath = listOf(result.compiledClasspath) + outputResources
+ val generatedResources =
+ outputResources
+ .walkTopDown()
+ .filter { it.isFile }
+ .map { FileResource(it.relativeTo(outputResources).path, it) }
+ .toList()
+ return CompilationStepResult(
+ success = result.exitCode == ExitCode.OK && !failureDueToWarnings,
+ generatedSourceRoots = generatedSources,
+ diagnostics = diagnostics,
+ nextCompilerArguments =
+ arguments.copy(sourceSets = arguments.sourceSets + generatedSources),
+ outputClasspath = outputClasspath,
+ generatedResources = generatedResources
+ )
+ }
+
+ companion object {
+ private const val JAVA_OUT_DIR = "generatedJava"
+ private const val KOTLIN_OUT_DIR = "generatedKotlin"
+ private const val CLASS_OUT_FOLDER_NAME = "class-out"
+ private const val RESOURCES_OUT_FOLDER_NAME = "ksp-compiler/resourceOutputDir"
+ }
+}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/Ksp2Compilation.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/Ksp2Compilation.kt
new file mode 100644
index 0000000..6f1b94b
--- /dev/null
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/Ksp2Compilation.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.util.compiler
+
+import androidx.room.compiler.processing.util.FileResource
+import androidx.room.compiler.processing.util.compiler.steps.CompilationStepArguments
+import androidx.room.compiler.processing.util.compiler.steps.CompilationStepResult
+import androidx.room.compiler.processing.util.compiler.steps.RawDiagnosticMessage
+import androidx.room.compiler.processing.util.compiler.steps.resolveDiagnostics
+import com.google.devtools.ksp.impl.KotlinSymbolProcessing
+import com.google.devtools.ksp.processing.KSPJvmConfig
+import com.google.devtools.ksp.processing.KSPLogger
+import com.google.devtools.ksp.processing.SymbolProcessorProvider
+import com.google.devtools.ksp.symbol.FileLocation
+import com.google.devtools.ksp.symbol.KSNode
+import com.google.devtools.ksp.symbol.NonExistLocation
+import java.io.File
+import java.io.PrintWriter
+import java.io.StringWriter
+import javax.tools.Diagnostic
+
+internal class Ksp2Compilation(
+ private val name: String,
+ private val symbolProcessorProviders: List<SymbolProcessorProvider>,
+ private val processorOptions: Map<String, String>
+) {
+ fun execute(workingDir: File, arguments: CompilationStepArguments): CompilationStepResult {
+ if (symbolProcessorProviders.isEmpty()) {
+ return CompilationStepResult.skip(arguments)
+ }
+
+ val kspConfig = createKspConfig(workingDir, arguments)
+ val kspDiagnostics = DiagnosticsCollectorKspLogger()
+ val exitCode =
+ KotlinSymbolProcessing(
+ kspConfig = kspConfig,
+ symbolProcessorProviders = symbolProcessorProviders,
+ logger = kspDiagnostics
+ )
+ .execute()
+ val generatedSources =
+ listOfNotNull(
+ workingDir.resolve(JAVA_SRC_OUT_FOLDER_NAME).toSourceSet(),
+ workingDir.resolve(KOTLIN_SRC_OUT_FOLDER_NAME).toSourceSet(),
+ )
+ val diagnostics =
+ resolveDiagnostics(
+ diagnostics = kspDiagnostics.messages,
+ sourceSets = arguments.sourceSets + generatedSources
+ )
+ val outputResources = workingDir.resolve(RESOURCES_OUT_FOLDER_NAME)
+ val outputClasspath = listOf(workingDir.resolve(CLASS_OUT_FOLDER_NAME))
+ val generatedResources =
+ outputResources
+ .walkTopDown()
+ .filter { it.isFile }
+ .map { FileResource(it.relativeTo(outputResources).path, it) }
+ .toList()
+ return CompilationStepResult(
+ success = exitCode == KotlinSymbolProcessing.ExitCode.OK,
+ generatedSourceRoots = generatedSources,
+ diagnostics = diagnostics,
+ nextCompilerArguments =
+ arguments.copy(sourceSets = arguments.sourceSets + generatedSources),
+ outputClasspath = outputClasspath,
+ generatedResources = generatedResources
+ )
+ }
+
+ private fun createKspConfig(workingDir: File, arguments: CompilationStepArguments) =
+ KSPJvmConfig.Builder()
+ .apply {
+ projectBaseDir = workingDir
+
+ sourceRoots =
+ arguments.sourceSets.filter { it.hasKotlinSource }.existingRoots().toList()
+ javaSourceRoots =
+ arguments.sourceSets.filter { it.hasJavaSource }.existingRoots().toList()
+
+ libraries = buildList {
+ if (arguments.inheritClasspaths) {
+ addAll(TestClasspath.inheritedClasspath)
+ }
+ addAll(arguments.additionalClasspaths)
+ }
+ jdkHome = File(System.getProperty("java.home"))
+
+ outputBaseDir = workingDir
+ javaOutputDir = workingDir.resolve(JAVA_SRC_OUT_FOLDER_NAME)
+ kotlinOutputDir = workingDir.resolve(KOTLIN_SRC_OUT_FOLDER_NAME)
+ resourceOutputDir = workingDir.resolve(RESOURCE_OUT_FOLDER_NAME)
+ classOutputDir = workingDir.resolve(CLASS_OUT_FOLDER_NAME)
+
+ cachesDir = workingDir.resolve(CACHE_FOLDER_NAME)
+
+ moduleName = ""
+
+ languageVersion = TestDefaultOptions.kotlinLanguageVersion.versionString
+ apiVersion = TestDefaultOptions.kotlinApiVersion.versionString
+ jvmTarget = TestDefaultOptions.jvmTarget.description
+ jvmDefaultMode = TestDefaultOptions.jvmDefaultMode.description
+
+ processorOptions = this@Ksp2Compilation.processorOptions
+ }
+ .build()
+
+ // We purposely avoid using MessageCollectorBasedKSPLogger to reduce our dependency on impls.
+ private class DiagnosticsCollectorKspLogger : KSPLogger {
+
+ val messages = mutableListOf<RawDiagnosticMessage>()
+
+ override fun error(message: String, symbol: KSNode?) {
+ messages.add(RawDiagnosticMessage(Diagnostic.Kind.ERROR, message, symbol.toLocation()))
+ }
+
+ override fun exception(e: Throwable) {
+ val writer = StringWriter()
+ e.printStackTrace(PrintWriter(writer))
+ messages.add(RawDiagnosticMessage(Diagnostic.Kind.ERROR, writer.toString(), null))
+ }
+
+ override fun info(message: String, symbol: KSNode?) {
+ messages.add(RawDiagnosticMessage(Diagnostic.Kind.NOTE, message, symbol.toLocation()))
+ }
+
+ override fun logging(message: String, symbol: KSNode?) {
+ messages.add(RawDiagnosticMessage(Diagnostic.Kind.NOTE, message, symbol.toLocation()))
+ }
+
+ override fun warn(message: String, symbol: KSNode?) {
+ messages.add(
+ RawDiagnosticMessage(Diagnostic.Kind.WARNING, message, symbol.toLocation())
+ )
+ }
+
+ private fun KSNode?.toLocation(): RawDiagnosticMessage.Location? {
+ val location = this?.location ?: return null
+ return when (location) {
+ is FileLocation ->
+ RawDiagnosticMessage.Location(
+ path = location.filePath,
+ line = location.lineNumber
+ )
+ NonExistLocation -> null
+ }
+ }
+ }
+
+ companion object {
+ private const val JAVA_SRC_OUT_FOLDER_NAME = "ksp-java-src-out"
+ private const val KOTLIN_SRC_OUT_FOLDER_NAME = "ksp-kotlin-src-out"
+ private const val RESOURCE_OUT_FOLDER_NAME = "ksp-resource-out"
+ private const val CACHE_FOLDER_NAME = "ksp-cache"
+ private const val CLASS_OUT_FOLDER_NAME = "class-out"
+ private const val RESOURCES_OUT_FOLDER_NAME = "ksp-compiler/resourceOutputDir"
+ }
+}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
index e1bf9a1..cc02a4f 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
@@ -105,3 +105,9 @@
} else {
null
}
+
+internal fun List<SourceSet>.existingRoots() =
+ this.asSequence().map { it.root }.filter { it.exists() }.distinct()
+
+internal fun List<SourceSet>.existingRootPaths() =
+ this.asSequence().map { it.root }.filter { it.exists() }.map { it.canonicalPath }.distinct()
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestClasspath.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestClasspath.kt
new file mode 100644
index 0000000..4bbe437
--- /dev/null
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestClasspath.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.util.compiler
+
+import androidx.room.compiler.processing.util.getSystemClasspaths
+import java.io.File
+import java.net.URLClassLoader
+
+/** Test runtime classpath helper. */
+internal object TestClasspath {
+ internal val inheritedClasspath by
+ lazy(LazyThreadSafetyMode.NONE) {
+ getClasspathFromClassloader(KotlinCliRunner::class.java.classLoader)
+ }
+
+ // Ported from
+ // https://github.com/google/compile-testing/blob/master/src/main/java/com/google/testing/compile/Compiler.java#L231
+ private fun getClasspathFromClassloader(referenceClassLoader: ClassLoader): List<File> {
+ val platformClassLoader: ClassLoader = ClassLoader.getPlatformClassLoader()
+ var currentClassloader = referenceClassLoader
+ val systemClassLoader = ClassLoader.getSystemClassLoader()
+
+ // Concatenate search paths from all classloaders in the hierarchy
+ // 'till the system classloader.
+ val classpaths: MutableSet<String> = LinkedHashSet()
+ while (true) {
+ if (currentClassloader === systemClassLoader) {
+ classpaths.addAll(getSystemClasspaths())
+ break
+ }
+ if (currentClassloader === platformClassLoader) {
+ break
+ }
+ check(currentClassloader is URLClassLoader) {
+ "Classpath for compilation could not be extracted since $currentClassloader " +
+ "is not an instance of URLClassloader"
+ }
+
+ // We only know how to extract classpaths from URLClassloaders.
+ currentClassloader.urLs.forEach { url ->
+ check(url.protocol == "file") {
+ "Given classloader consists of classpaths which are unsupported for " +
+ "compilation."
+ }
+ classpaths.add(url.path)
+ }
+ currentClassloader = currentClassloader.parent
+ }
+ return classpaths.map { File(it) }.filter { it.exists() }
+ }
+}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestDefaultOptions.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestDefaultOptions.kt
new file mode 100644
index 0000000..48ea099
--- /dev/null
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestDefaultOptions.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.util.compiler
+
+import org.jetbrains.kotlin.config.ApiVersion
+import org.jetbrains.kotlin.config.JvmDefaultMode
+import org.jetbrains.kotlin.config.JvmTarget
+import org.jetbrains.kotlin.config.LanguageVersion
+
+/** Default argument / options across the steps and test compiler infra. */
+internal object TestDefaultOptions {
+ // TODO(kuanyingchou): Change it back to follow AndroidX once it's updated to K2.
+ internal val kotlinLanguageVersion = LanguageVersion.KOTLIN_2_0
+ internal val kotlinApiVersion = ApiVersion.createByLanguageVersion(kotlinLanguageVersion)
+ internal val jvmTarget = JvmTarget.JVM_1_8
+ internal val jvmDefaultMode = JvmDefaultMode.ALL_COMPATIBILITY
+}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKapt3Registrar.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKapt3Registrar.kt
deleted file mode 100644
index 18d556d..0000000
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKapt3Registrar.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright 2010-2016 JetBrains s.r.o.
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room.compiler.processing.util.compiler
-
-import java.io.File
-import javax.annotation.processing.Processor
-import org.jetbrains.kotlin.base.kapt3.KaptFlag
-import org.jetbrains.kotlin.base.kapt3.KaptOptions
-import org.jetbrains.kotlin.base.kapt3.logString
-import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
-import org.jetbrains.kotlin.cli.common.messages.MessageCollector
-import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
-import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
-import org.jetbrains.kotlin.com.intellij.mock.MockProject
-import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
-import org.jetbrains.kotlin.config.CompilerConfiguration
-import org.jetbrains.kotlin.config.JVMConfigurationKeys
-import org.jetbrains.kotlin.container.StorageComponentContainer
-import org.jetbrains.kotlin.container.useInstance
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
-import org.jetbrains.kotlin.descriptors.ModuleDescriptor
-import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
-import org.jetbrains.kotlin.kapt3.AbstractKapt3Extension
-import org.jetbrains.kotlin.kapt3.base.LoadedProcessors
-import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType
-import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor
-import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedKaptLogger
-import org.jetbrains.kotlin.kapt3.util.doOpenInternalPackagesIfRequired
-import org.jetbrains.kotlin.platform.TargetPlatform
-import org.jetbrains.kotlin.platform.jvm.isJvm
-import org.jetbrains.kotlin.resolve.jvm.ReplaceWithSupertypeAnonymousTypeTransformer
-import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
-import org.jetbrains.kotlin.resolve.jvm.extensions.PartialAnalysisHandlerExtension
-import org.jetbrains.kotlin.types.KotlinType
-
-/**
- * Registers the KAPT component for the kotlin compilation.
- *
- * mostly taken from
- * https://github.com/JetBrains/kotlin/blob/master/plugins/kapt3/kapt3-compiler/src/
- * org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt
- */
-@Suppress("DEPRECATION") // TODO: Migrate ComponentRegistrar to CompilerPluginRegistrar
-@OptIn(ExperimentalCompilerApi::class)
-internal class TestKapt3Registrar(
- val processors: List<Processor>,
- val baseOptions: KaptOptions.Builder,
- val messageCollector: MessageCollector
-) : @Suppress("DEPRECATION") org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar {
- override fun registerProjectComponents(
- project: MockProject,
- configuration: CompilerConfiguration
- ) {
- doOpenInternalPackagesIfRequired()
- val contentRoots = configuration[CLIConfigurationKeys.CONTENT_ROOTS] ?: emptyList()
-
- val optionsBuilder =
- baseOptions.apply {
- projectBaseDir = project.basePath?.let(::File)
- compileClasspath.addAll(
- contentRoots.filterIsInstance<JvmClasspathRoot>().map { it.file }
- )
- javaSourceRoots.addAll(
- contentRoots.filterIsInstance<JavaSourceRoot>().map { it.file }
- )
- classesOutputDir =
- classesOutputDir ?: configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY)
- }
-
- val logger =
- MessageCollectorBackedKaptLogger(
- isVerbose = optionsBuilder.flags.contains(KaptFlag.VERBOSE),
- isInfoAsWarnings = optionsBuilder.flags.contains(KaptFlag.INFO_AS_WARNINGS),
- messageCollector = messageCollector
- )
-
- val options = optionsBuilder.build()
-
- options.sourcesOutputDir.mkdirs()
-
- if (options[KaptFlag.VERBOSE]) {
- logger.info(options.logString())
- }
-
- val kapt3AnalysisCompletedHandlerExtension =
- object :
- AbstractKapt3Extension(
- options = options,
- logger = logger,
- compilerConfiguration = configuration
- ) {
- override fun loadProcessors(): LoadedProcessors {
- return LoadedProcessors(
- processors =
- processors.map {
- IncrementalProcessor(
- processor = it,
- kind = DeclaredProcType.NON_INCREMENTAL,
- logger = logger
- )
- },
- classLoader = TestKapt3Registrar::class.java.classLoader
- )
- }
- }
-
- AnalysisHandlerExtension.registerExtension(project, kapt3AnalysisCompletedHandlerExtension)
- StorageComponentContainerContributor.registerExtension(
- project,
- KaptComponentContributor(kapt3AnalysisCompletedHandlerExtension)
- )
- }
-
- class KaptComponentContributor(private val analysisExtension: PartialAnalysisHandlerExtension) :
- StorageComponentContainerContributor {
- override fun registerModuleComponents(
- container: StorageComponentContainer,
- platform: TargetPlatform,
- moduleDescriptor: ModuleDescriptor
- ) {
- if (!platform.isJvm()) return
- container.useInstance(
- object : ReplaceWithSupertypeAnonymousTypeTransformer() {
- override fun transformAnonymousType(
- descriptor: DeclarationDescriptorWithVisibility,
- type: KotlinType
- ): KotlinType? {
- if (!analysisExtension.analyzePartially) return null
- return super.transformAnonymousType(descriptor, type)
- }
- }
- )
- }
- }
-}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKotlinCompiler.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKotlinCompiler.kt
index 8913ac9..2849e59 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKotlinCompiler.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKotlinCompiler.kt
@@ -29,6 +29,8 @@
import java.io.File
import javax.annotation.processing.Processor
import javax.tools.Diagnostic
+import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
+import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
/** Compilation runner for kotlin using kotlin CLI tool */
data class TestCompilationArguments(
@@ -70,6 +72,13 @@
val generatedResources: List<Resource>,
)
+@OptIn(ExperimentalCompilerApi::class)
+internal class PluginRegistrarArguments(
+ @Suppress("DEPRECATION")
+ val k1Registrars: List<org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar>,
+ val k2Registrars: List<CompilerPluginRegistrar>
+)
+
/** Ensures the list of sources has at least 1 kotlin file, if not, adds one. */
internal fun TestCompilationArguments.withAtLeastOneKotlinSource(): TestCompilationArguments {
val hasKotlinSource = sources.any { it is Source.KotlinSource }
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKspRegistrar.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKspRegistrar.kt
index 074079e..0deef7a 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKspRegistrar.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKspRegistrar.kt
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package androidx.room.compiler.processing.util.compiler
import com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension
@@ -64,15 +63,12 @@
?: kspWorkingDir.resolve(KspCliOption.RESOURCE_OUTPUT_DIR_OPTION.optionName)
cachesDir =
cachesDir ?: kspWorkingDir.resolve(KspCliOption.CACHES_DIR_OPTION.optionName)
-
kspOutputDir =
kspOutputDir ?: kspWorkingDir.resolve(KspCliOption.KSP_OUTPUT_DIR_OPTION.optionName)
val contentRoots = configuration[CLIConfigurationKeys.CONTENT_ROOTS] ?: emptyList()
-
compileClasspath.addAll(
contentRoots.filterIsInstance<JvmClasspathRoot>().map { it.file }
)
-
javaSourceRoots.addAll(contentRoots.filterIsInstance<JavaSourceRoot>().map { it.file })
}
val logger =
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/JavaSourceCompilationStep.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/JavaSourceCompilationStep.kt
index bf807f6..9551279 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/JavaSourceCompilationStep.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/JavaSourceCompilationStep.kt
@@ -25,9 +25,11 @@
import javax.tools.JavaFileObject
/**
- * Compiles java sources. Note that this does not run java annotation processors. They are run in
- * the KAPT step for consistency. When a test is run with purely java sources, it uses the google
- * compile testing library directly instead of the kotlin compilation pipeline.
+ * Compiles Java sources.
+ *
+ * Note that this does not run Java annotation processors. They are run in the KAPT step for
+ * consistency. When a test is run with purely Java sources, it uses google-compile-testing library
+ * directly instead of the Kotlin compilation pipeline.
*/
internal object JavaSourceCompilationStep : KotlinCompilationStep {
override val name = "javaSourceCompilation"
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KaptCompilationStep.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KaptCompilationStep.kt
index ae6d781..dc620b9 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KaptCompilationStep.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KaptCompilationStep.kt
@@ -17,61 +17,33 @@
package androidx.room.compiler.processing.util.compiler.steps
import androidx.room.compiler.processing.util.FileResource
-import androidx.room.compiler.processing.util.compiler.DiagnosticsMessageCollector
import androidx.room.compiler.processing.util.compiler.KotlinCliRunner
-import androidx.room.compiler.processing.util.compiler.TestKapt3Registrar
import androidx.room.compiler.processing.util.compiler.toSourceSet
+import java.io.ByteArrayOutputStream
import java.io.File
+import java.io.ObjectOutputStream
+import java.util.Base64
import javax.annotation.processing.Processor
-import org.jetbrains.kotlin.base.kapt3.AptMode
-import org.jetbrains.kotlin.base.kapt3.KaptFlag
-import org.jetbrains.kotlin.base.kapt3.KaptOptions
import org.jetbrains.kotlin.cli.common.ExitCode
-import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
-import org.jetbrains.kotlin.compiler.plugin.parseLegacyPluginOption
+import org.jetbrains.kotlin.com.intellij.util.PathUtil
+import org.jetbrains.kotlin.kapt.cli.CliToolOption
+import org.jetbrains.kotlin.kapt.cli.KaptCliOption
+import org.jetbrains.kotlin.kapt3.base.AptMode
-/** Runs KAPT to run annotation processors. */
+/** Runs KAPT to run Java annotation processors. */
internal class KaptCompilationStep(
private val annotationProcessors: List<Processor>,
private val processorOptions: Map<String, String>,
) : KotlinCompilationStep {
override val name = "kapt"
- private fun createKaptArgs(
- workingDir: File,
- javacArguments: List<String>,
- kotlincArguments: List<String>
- ): KaptOptions.Builder {
- return KaptOptions.Builder().also {
- it.stubsOutputDir = workingDir.resolve("kapt-stubs") // IGNORED
- it.sourcesOutputDir = workingDir.resolve(JAVA_SRC_OUT_FOLDER_NAME)
- // Compiled classes don't end up here but generated resources do.
- it.classesOutputDir = workingDir.resolve(RESOURCES_OUT_FOLDER_NAME)
- it.projectBaseDir = workingDir
- it.processingOptions["kapt.kotlin.generated"] =
- workingDir.resolve(KOTLIN_SRC_OUT_FOLDER_NAME).also { it.mkdirs() }.canonicalPath
- it.processingOptions.putAll(processorOptions)
- it.mode = AptMode.STUBS_AND_APT
- it.processors.addAll(annotationProcessors.map { it::class.java.name })
- // NOTE: this does not work very well until the following bug is fixed
- // https://youtrack.jetbrains.com/issue/KT-47934
- it.flags.add(KaptFlag.MAP_DIAGNOSTIC_LOCATIONS)
-
- if (
- getPluginOptions(KAPT_PLUGIN_ID, kotlincArguments)
- .getOrDefault("correctErrorTypes", "false") == "true"
- ) {
- it.flags.add(KaptFlag.CORRECT_ERROR_TYPES)
- }
-
- javacArguments.forEach { javacArg ->
- it.javacOptions[javacArg.substringBefore("=")] =
- javacArg.substringAfter("=", missingDelimiterValue = "")
- }
+ init {
+ check(annotationProcessors.size <= 10) {
+ "Only 10 annotation processor can be loaded for test compilation for now, but " +
+ "requested ${annotationProcessors.size}. Tell Dany to support more!"
}
}
- @OptIn(ExperimentalCompilerApi::class)
override fun execute(
workingDir: File,
arguments: CompilationStepArguments
@@ -79,34 +51,38 @@
if (annotationProcessors.isEmpty()) {
return CompilationStepResult.skip(arguments)
}
- val kaptMessages = DiagnosticsMessageCollector(name)
+
+ val kaptArgs = buildList {
+ // Both 'kotlin-annotation-processing.jar' and
+ // 'kotlin-annotation-processing-embeddable.jar' contain the KAPT plugin that must
+ // specified so we use the `CliToolOption` class just as `KaptCli` does.
+ val pathToAnnotationJar = PathUtil.getJarPathForClass(CliToolOption::class.java)
+ add("-Xplugin=$pathToAnnotationJar")
+ createKaptCliOptions(workingDir, arguments).forEach { (option, value) ->
+ add("-P")
+ add("plugin:$KAPT_PLUGIN_ID:${option.optionName}=$value")
+ }
+ }
+ val argumentsWithKapt =
+ arguments.copy(kotlincArguments = arguments.kotlincArguments + kaptArgs)
val result =
- KotlinCliRunner.runKotlinCli(
- arguments = arguments, // output is ignored,
- destinationDir = workingDir.resolve(CLASS_OUT_FOLDER_NAME),
- pluginRegistrars =
- listOf(
- TestKapt3Registrar(
- processors = annotationProcessors,
- baseOptions =
- createKaptArgs(
- workingDir,
- arguments.javacArguments,
- arguments.kotlincArguments
- ),
- messageCollector = kaptMessages
- )
- )
- )
+ try {
+ delegateProcessors.set(annotationProcessors)
+ KotlinCliRunner.runKotlinCli(
+ arguments = argumentsWithKapt,
+ destinationDir = workingDir.resolve(CLASS_OUT_FOLDER_NAME)
+ )
+ } finally {
+ delegateProcessors.remove()
+ }
val generatedSources =
listOfNotNull(
workingDir.resolve(JAVA_SRC_OUT_FOLDER_NAME).toSourceSet(),
workingDir.resolve(KOTLIN_SRC_OUT_FOLDER_NAME).toSourceSet()
)
-
val diagnostics =
resolveDiagnostics(
- diagnostics = result.diagnostics + kaptMessages.getDiagnostics(),
+ diagnostics = result.diagnostics,
sourceSets = arguments.sourceSets + generatedSources
)
val outputResources = workingDir.resolve(RESOURCES_OUT_FOLDER_NAME)
@@ -128,28 +104,121 @@
)
}
+ private fun createKaptCliOptions(
+ workingDir: File,
+ arguments: CompilationStepArguments
+ ): List<Pair<KaptCliOption, String>> = buildList {
+ add(KaptCliOption.APT_MODE_OPTION to AptMode.STUBS_AND_APT.stringValue)
+ add(
+ KaptCliOption.SOURCE_OUTPUT_DIR_OPTION to
+ workingDir.resolve(JAVA_SRC_OUT_FOLDER_NAME).absolutePath
+ )
+ // Compiled classes don't end up here but generated resources do.
+ add(
+ KaptCliOption.CLASS_OUTPUT_DIR_OPTION to
+ workingDir.resolve(RESOURCES_OUT_FOLDER_NAME).absolutePath
+ )
+ add(
+ KaptCliOption.STUBS_OUTPUT_DIR_OPTION to
+ workingDir.resolve(STUBS_OUT_FOLDER_NAME).absolutePath
+ )
+
+ // 'apclasspath' is not used since FQN are specified in 'processors', but if left unset
+ // KAPT does not try to load processors at all.
+ add(KaptCliOption.ANNOTATION_PROCESSOR_CLASSPATH_OPTION to "empty")
+ List(annotationProcessors.size) { index ->
+ add(
+ KaptCliOption.ANNOTATION_PROCESSORS_OPTION to
+ TestDelegateProcessor.KaptTestDelegateAP0::class.java.name.dropLast(1) + index
+ )
+ }
+
+ val apOptionsMap = buildMap {
+ // Kotlin generated source output location is specified through this special
+ // annotation processor option.
+ put(
+ "kapt.kotlin.generated",
+ workingDir.resolve(KOTLIN_SRC_OUT_FOLDER_NAME).also { it.mkdirs() }.canonicalPath
+ )
+ putAll(processorOptions)
+ }
+ // We *need* to use the deprecated 'apoptions' since it supports multiple values as
+ // opposed to 'apOption' that only accepts one value and also is
+ // allowMultipleOccurrences = false
+ @Suppress("DEPRECATION")
+ add(KaptCliOption.APT_OPTIONS_OPTION to apOptionsMap.base64Encoded())
+
+ val javacOptionsMap =
+ arguments.javacArguments.associate { rawArg ->
+ val keyValuePair = rawArg.split('=', limit = 2).takeIf { it.size == 2 }
+ if (keyValuePair != null) {
+ keyValuePair[0] to keyValuePair[1]
+ } else {
+ rawArg to ""
+ }
+ }
+ // We *need* to use the deprecated 'javacArguments' since it supports multiple values as
+ // opposed to 'javacOption' that only accepts one value and also is
+ // allowMultipleOccurrences = false
+ @Suppress("DEPRECATION")
+ add(KaptCliOption.JAVAC_CLI_OPTIONS_OPTION to javacOptionsMap.base64Encoded())
+
+ // NOTE: This does not work very well until the following bug is fixed
+ // https://youtrack.jetbrains.com/issue/KT-47934
+ add(KaptCliOption.MAP_DIAGNOSTIC_LOCATIONS_OPTION to "true")
+ }
+
+ // As suggested by https://kotlinlang.org/docs/kapt.html#ap-javac-options-encoding
+ private fun Map<String, String>.base64Encoded(): String {
+ val os = ByteArrayOutputStream()
+ val oos = ObjectOutputStream(os)
+ oos.writeInt(size)
+ for ((key, value) in entries) {
+ oos.writeUTF(key)
+ oos.writeUTF(value)
+ }
+ oos.flush()
+ return Base64.getEncoder().encodeToString(os.toByteArray())
+ }
+
companion object {
private const val JAVA_SRC_OUT_FOLDER_NAME = "kapt-java-src-out"
private const val KOTLIN_SRC_OUT_FOLDER_NAME = "kapt-kotlin-src-out"
+ private const val STUBS_OUT_FOLDER_NAME = "kapt-stubs-out"
private const val RESOURCES_OUT_FOLDER_NAME = "kapt-classes-out"
private const val CLASS_OUT_FOLDER_NAME = "class-out"
private const val KAPT_PLUGIN_ID = "org.jetbrains.kotlin.kapt3"
-
- internal fun getPluginOptions(
- pluginId: String,
- kotlincArguments: List<String>
- ): Map<String, String> {
- val options =
- kotlincArguments
- .dropLast(1)
- .zip(kotlincArguments.drop(1))
- .filter { it.first == "-P" }
- .mapNotNull { parseLegacyPluginOption(it.second) }
- val filteredOptionsMap =
- options
- .filter { it.pluginId == pluginId }
- .associateBy({ it.optionName }, { it.value })
- return filteredOptionsMap
- }
}
}
+
+/** The list of processors to delegate to during the test compilation. */
+private val delegateProcessors = ThreadLocal<List<Processor>>()
+
+/**
+ * These delegate classes may seem unused but will be instantiated by KAPT via reflection and
+ * through their no-arg constructor, and we use them to delegate to actual processors provided for
+ * the test compilation. Note that the processor to delegate to is index based and obtained from the
+ * thread local [delegateProcessors] that is set before compiler invocation.
+ */
+@Suppress("UNUSED")
+sealed class TestDelegateProcessor(val delegate: Processor) : Processor by delegate {
+ class KaptTestDelegateAP0 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[0])
+
+ class KaptTestDelegateAP1 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[1])
+
+ class KaptTestDelegateAP2 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[2])
+
+ class KaptTestDelegateAP3 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[3])
+
+ class KaptTestDelegateAP4 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[4])
+
+ class KaptTestDelegateAP5 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[5])
+
+ class KaptTestDelegateAP6 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[6])
+
+ class KaptTestDelegateAP7 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[7])
+
+ class KaptTestDelegateAP8 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[8])
+
+ class KaptTestDelegateAP9 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[9])
+}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinCompilationStep.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinCompilationStep.kt
index 1017983..558afec 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinCompilationStep.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinCompilationStep.kt
@@ -25,8 +25,11 @@
import javax.tools.Diagnostic
/**
- * Kotlin compilation is run in multiple steps: process KSP process KAPT compile kotlin sources
- * compile java sources
+ * Kotlin compilation is run in multiple steps:
+ * * process KSP
+ * * process KAPT
+ * * compile kotlin sources
+ * * compile java sources
*
* Each step implements the [KotlinCompilationStep] interfaces and provides the arguments for the
* following step.
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinSourceCompilationStep.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinSourceCompilationStep.kt
index 779070e..17b431c 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinSourceCompilationStep.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinSourceCompilationStep.kt
@@ -19,17 +19,15 @@
import androidx.room.compiler.processing.util.compiler.KotlinCliRunner
import java.io.File
import org.jetbrains.kotlin.cli.common.ExitCode
-import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
/**
- * Compiles kotlin sources.
+ * Compiles Kotlin sources.
*
- * Note that annotation/symbol processors are not run by this step.
+ * Note that annotation / symbol processors are not run by this step.
*/
internal object KotlinSourceCompilationStep : KotlinCompilationStep {
override val name = "kotlinSourceCompilation"
- @OptIn(ExperimentalCompilerApi::class)
override fun execute(
workingDir: File,
arguments: CompilationStepArguments
@@ -41,7 +39,6 @@
KotlinCliRunner.runKotlinCli(
arguments = arguments,
destinationDir = workingDir.resolve(CLASS_OUT_FOLDER_NAME),
- pluginRegistrars = emptyList()
)
val diagnostics =
resolveDiagnostics(diagnostics = result.diagnostics, sourceSets = arguments.sourceSets)
@@ -55,7 +52,8 @@
listOf(workingDir.resolve(CLASS_OUT_FOLDER_NAME)) +
arguments.additionalClasspaths,
// NOTE: ideally, we should remove kotlin sources but we know that there are no
- // more kotlin steps so we skip unnecessary work
+ // more
+ // kotlin steps so we skip unnecessary work
sourceSets = arguments.sourceSets
),
outputClasspath = listOf(result.compiledClasspath),
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KspCompilationStep.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KspCompilationStep.kt
index 1f77c72..fdda61e 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KspCompilationStep.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KspCompilationStep.kt
@@ -16,92 +16,31 @@
package androidx.room.compiler.processing.util.compiler.steps
-import androidx.room.compiler.processing.util.FileResource
-import androidx.room.compiler.processing.util.compiler.DiagnosticsMessageCollector
import androidx.room.compiler.processing.util.compiler.KotlinCliRunner
-import androidx.room.compiler.processing.util.compiler.TestKspRegistrar
-import androidx.room.compiler.processing.util.compiler.toSourceSet
-import com.google.devtools.ksp.KspOptions
+import androidx.room.compiler.processing.util.compiler.Ksp1Compilation
+import androidx.room.compiler.processing.util.compiler.Ksp2Compilation
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import java.io.File
-import org.jetbrains.kotlin.cli.common.ExitCode
-import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
+import org.jetbrains.kotlin.config.LanguageVersion
-/** Runs the Symbol Processors */
+/** Runs KSP to run the Symbol Processors */
internal class KspCompilationStep(
private val symbolProcessorProviders: List<SymbolProcessorProvider>,
private val processorOptions: Map<String, String>
) : KotlinCompilationStep {
override val name: String = "ksp"
- private fun createKspOptions(workingDir: File): KspOptions.Builder {
- return KspOptions.Builder().apply {
- this.javaOutputDir = workingDir.resolve(JAVA_OUT_DIR)
- this.kotlinOutputDir = workingDir.resolve(KOTLIN_OUT_DIR)
- this.processingOptions.putAll(processorOptions)
- }
- }
-
- @OptIn(ExperimentalCompilerApi::class)
override fun execute(
workingDir: File,
arguments: CompilationStepArguments
): CompilationStepResult {
- if (symbolProcessorProviders.isEmpty()) {
- return CompilationStepResult.skip(arguments)
+ val languageVersion = KotlinCliRunner.getLanguageVersion(arguments.kotlincArguments)
+ return if (languageVersion < LanguageVersion.KOTLIN_2_0) {
+ Ksp1Compilation(name, symbolProcessorProviders, processorOptions)
+ .execute(workingDir, arguments)
+ } else {
+ Ksp2Compilation(name, symbolProcessorProviders, processorOptions)
+ .execute(workingDir, arguments)
}
- val kspMessages = DiagnosticsMessageCollector(name)
- val result =
- KotlinCliRunner.runKotlinCli(
- arguments = arguments,
- destinationDir = workingDir.resolve(CLASS_OUT_FOLDER_NAME),
- pluginRegistrars =
- listOf(
- TestKspRegistrar(
- kspWorkingDir = workingDir.resolve("ksp-compiler"),
- baseOptions = createKspOptions(workingDir),
- processorProviders = symbolProcessorProviders,
- messageCollector = kspMessages
- )
- ),
- )
- // workaround for https://github.com/google/ksp/issues/623
- val failureDueToWarnings =
- result.kotlinCliArguments.allWarningsAsErrors && kspMessages.hasWarnings()
-
- val generatedSources =
- listOfNotNull(
- workingDir.resolve(KOTLIN_OUT_DIR).toSourceSet(),
- workingDir.resolve(JAVA_OUT_DIR).toSourceSet(),
- )
- val diagnostics =
- resolveDiagnostics(
- diagnostics = result.diagnostics + kspMessages.getDiagnostics(),
- sourceSets = arguments.sourceSets + generatedSources
- )
- val outputResources = workingDir.resolve(RESOURCES_OUT_FOLDER_NAME)
- val outputClasspath = listOf(result.compiledClasspath) + outputResources
- val generatedResources =
- outputResources
- .walkTopDown()
- .filter { it.isFile }
- .map { FileResource(it.relativeTo(outputResources).path, it) }
- .toList()
- return CompilationStepResult(
- success = result.exitCode == ExitCode.OK && !failureDueToWarnings,
- generatedSourceRoots = generatedSources,
- diagnostics = diagnostics,
- nextCompilerArguments =
- arguments.copy(sourceSets = arguments.sourceSets + generatedSources),
- outputClasspath = outputClasspath,
- generatedResources = generatedResources
- )
- }
-
- companion object {
- private const val JAVA_OUT_DIR = "generatedJava"
- private const val KOTLIN_OUT_DIR = "generatedKotlin"
- private const val CLASS_OUT_FOLDER_NAME = "class-out"
- private const val RESOURCES_OUT_FOLDER_NAME = "ksp-compiler/resourceOutputDir"
}
}
diff --git a/room/room-compiler-processing-testing/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar b/room/room-compiler-processing-testing/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
new file mode 100644
index 0000000..57a62dc
--- /dev/null
+++ b/room/room-compiler-processing-testing/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
@@ -0,0 +1,17 @@
+#
+# Copyright 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+androidx.room.compiler.processing.util.compiler.DelegatingTestRegistrar$K2Registrar
\ No newline at end of file
diff --git a/room/room-compiler-processing-testing/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar b/room/room-compiler-processing-testing/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
index ffaca31..67086aa 100644
--- a/room/room-compiler-processing-testing/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
+++ b/room/room-compiler-processing-testing/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
@@ -14,4 +14,4 @@
# limitations under the License.
#
-androidx.room.compiler.processing.util.compiler.DelegatingTestRegistrar
\ No newline at end of file
+androidx.room.compiler.processing.util.compiler.DelegatingTestRegistrar$K1Registrar
\ No newline at end of file
diff --git a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt
index 600845d..321cc80 100644
--- a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt
+++ b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt
@@ -25,9 +25,9 @@
import androidx.room.compiler.processing.XProcessingStep
import androidx.room.compiler.processing.javac.JavacBasicAnnotationProcessor
import androidx.room.compiler.processing.ksp.KspBasicAnnotationProcessor
+import androidx.room.compiler.processing.util.compiler.KotlinCliRunner
import androidx.room.compiler.processing.util.compiler.TestCompilationArguments
import androidx.room.compiler.processing.util.compiler.compile
-import androidx.room.compiler.processing.util.compiler.steps.KaptCompilationStep
import com.google.common.truth.Truth.assertThat
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
@@ -318,7 +318,10 @@
)
runProcessorTest(
sources = listOf(src),
- kotlincArguments = listOf("-Werror"),
+ // TODO(kuanyingchou): Remove the "1.9" args when we move to KAPT4. Our processor
+ // doesn't get to run with KAPT3 and K2 as we pass "-Werror" and we got warning:
+ // "Kapt currently doesn't support language version 2.0+. Falling back to 1.9."
+ kotlincArguments = listOf("-Werror", "-language-version=1.9", "-api-version=1.9"),
javacArguments = listOf("-Werror") // needed for kapt as it uses javac,
) { invocation ->
invocation.processingEnv.messager.printMessage(Diagnostic.Kind.WARNING, "some warning")
@@ -364,47 +367,46 @@
@Test
fun testPluginOptions() {
- KaptCompilationStep.getPluginOptions(
+ KotlinCliRunner.getPluginOptions(
"org.jetbrains.kotlin.kapt3",
listOf("-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true")
)
.let { options -> assertThat(options).containsExactly("correctErrorTypes", "true") }
// zero args
- KaptCompilationStep.getPluginOptions("org.jetbrains.kotlin.kapt3", emptyList()).let {
- options ->
+ KotlinCliRunner.getPluginOptions("org.jetbrains.kotlin.kapt3", emptyList()).let { options ->
assertThat(options).isEmpty()
}
// odd number of args
- KaptCompilationStep.getPluginOptions(
+ KotlinCliRunner.getPluginOptions(
"org.jetbrains.kotlin.kapt3",
listOf("-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true", "-verbose")
)
.let { options -> assertThat(options).containsExactly("correctErrorTypes", "true") }
// illegal format (missing "=")
- KaptCompilationStep.getPluginOptions(
+ KotlinCliRunner.getPluginOptions(
"org.jetbrains.kotlin.kapt3",
listOf("-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypestrue")
)
.let { options -> assertThat(options).isEmpty() }
// illegal format (missing "-P")
- KaptCompilationStep.getPluginOptions(
+ KotlinCliRunner.getPluginOptions(
"org.jetbrains.kotlin.kapt3",
listOf("plugin:org.jetbrains.kotlin.kapt3:correctErrorTypestrue")
)
.let { options -> assertThat(options).isEmpty() }
// illegal format (wrong plugin id)
- KaptCompilationStep.getPluginOptions(
+ KotlinCliRunner.getPluginOptions(
"org.jetbrains.kotlin.kapt3",
listOf("-P", "plugin:abc:correctErrorTypes=true")
)
.let { options -> assertThat(options).isEmpty() }
- KaptCompilationStep.getPluginOptions(
+ KotlinCliRunner.getPluginOptions(
"org.jetbrains.kotlin.kapt3",
listOf(
"-P",
diff --git a/room/room-compiler-processing/build.gradle b/room/room-compiler-processing/build.gradle
index 2d110d8..1890458 100644
--- a/room/room-compiler-processing/build.gradle
+++ b/room/room-compiler-processing/build.gradle
@@ -71,7 +71,7 @@
}
dependencies {
- api(libs.kotlinStdlib)
+ api(libs.kotlinStdlibJdk8)
api(libs.javapoet)
api(libs.kotlinPoet)
api(libs.kotlinPoetJavaPoet)
@@ -82,14 +82,12 @@
exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib"
}
implementation(libs.kspApi)
- implementation(libs.kotlinStdlibJdk8) // KSP defines older version as dependency, force update.
testImplementation("androidx.annotation:annotation:1.8.1")
testImplementation(libs.googleCompileTesting)
testImplementation(libs.junit)
testImplementation(libs.jsr250)
- testImplementation(libs.ksp)
- testImplementation(libs.kotlinMetadataJvm)
+ testImplementation(libs.kotlinMetadataJvm) // Due to being shadowed in main dependency
testImplementation(libs.testParameterInjector)
testImplementation(project(":room:room-compiler-processing-testing"))
testImplementation(project(":internal-testutils-common"))
@@ -109,6 +107,9 @@
tasks.withType(Test).configureEach { test ->
test.maxParallelForks(2)
test.systemProperty("androidx.room.compiler.processing.strict", "true")
+ // With the move to K2 and KSP2 the memory usage has increased so we enlarge the heap
+ // to prevent OOM while running all the tests in one go.
+ test.maxHeapSize("8g")
}
androidx {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt
index 5326743..3e57c9c 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt
@@ -17,6 +17,7 @@
package androidx.room.compiler.processing
import androidx.kruth.assertThat
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.compileFiles
import androidx.room.compiler.processing.util.getField
@@ -64,7 +65,11 @@
// sources and javac fails to resolve metadata
val placeholder = Source.kotlin("MyPlaceholder.kt", "")
val dependency = compileFiles(listOf(kotlinSource, javaSource))
- runProcessorTest(sources = listOf(placeholder), classpath = dependency) { invocation ->
+ runProcessorTest(
+ sources = listOf(placeholder),
+ classpath = dependency,
+ kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
+ ) { invocation ->
val kotlinSubject = invocation.processingEnv.requireTypeElement("foo.bar.KotlinSubject")
assertThat(kotlinSubject.getField("prop").fallbackLocationText)
.isEqualTo("prop in foo.bar.KotlinSubject")
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/InternalModifierTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/InternalModifierTest.kt
index fe3f93a..0b80372 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/InternalModifierTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/InternalModifierTest.kt
@@ -18,6 +18,7 @@
import androidx.kruth.assertThat
import androidx.room.compiler.processing.util.CompilationTestCapabilities
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.compileFiles
import androidx.room.compiler.processing.util.runKaptTest
@@ -141,6 +142,7 @@
runKspTest(
sources = listOf(source),
classpath = classpath,
+ kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS,
config = config,
) { invocation ->
kspResult = traverse(invocation.processingEnv)
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
index 4949386..6cb5d28 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
@@ -19,6 +19,7 @@
import androidx.kruth.assertThat
import androidx.room.compiler.processing.javac.JavacMethodElement
import androidx.room.compiler.processing.javac.JavacTypeElement
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compileFiles
@@ -171,7 +172,7 @@
"""
.trimIndent()
)
- overridesCheck(source)
+ overridesCheck(source, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS)
}
@Test
@@ -467,7 +468,11 @@
}
@Suppress("NAME_SHADOWING") // intentional
- private fun overridesCheck(vararg sources: Source, ignoreInheritedMethods: Boolean = false) {
+ private fun overridesCheck(
+ vararg sources: Source,
+ ignoreInheritedMethods: Boolean = false,
+ kotlincArgs: List<String> = emptyList()
+ ) {
val (sources: List<Source>, classpath: List<File>) =
if (preCompiledCode) {
emptyList<Source>() to compileFiles(sources.toList())
@@ -483,7 +488,8 @@
)
runProcessorTest(
sources = sources + Source.kotlin("Placeholder.kt", ""),
- classpath = classpath
+ classpath = classpath,
+ kotlincArguments = kotlincArgs
) { invocation ->
val (target, methods) = invocation.getOverrideTestTargets(ignoreInheritedMethods)
methods.forEachIndexed { index, method ->
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
index 663815d..6b27602 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
@@ -31,6 +31,7 @@
import androidx.room.compiler.processing.testcode.OtherAnnotation
import androidx.room.compiler.processing.testcode.RepeatableJavaAnnotation
import androidx.room.compiler.processing.testcode.TestSuppressWarnings
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compileFiles
@@ -47,7 +48,11 @@
@RunWith(Parameterized::class)
class XAnnotationBoxTest(private val preCompiled: Boolean) {
- private fun runTest(sources: List<Source>, handler: (XTestInvocation) -> Unit) {
+ private fun runTest(
+ sources: List<Source>,
+ kotlincArgs: List<String> = emptyList(),
+ handler: (XTestInvocation) -> Unit
+ ) {
if (preCompiled) {
val compiled = compileFiles(sources)
val hasKotlinSources = sources.any { it is Source.KotlinSource }
@@ -60,9 +65,14 @@
val newSources =
kotlinSources +
Source.java("PlaceholderJava", "public class " + "PlaceholderJava {}")
- runProcessorTest(sources = newSources, handler = handler, classpath = compiled)
+ runProcessorTest(
+ sources = newSources,
+ handler = handler,
+ classpath = compiled,
+ kotlincArguments = kotlincArgs
+ )
} else {
- runProcessorTest(sources = sources, handler = handler)
+ runProcessorTest(sources = sources, handler = handler, kotlincArguments = kotlincArgs)
}
}
@@ -212,7 +222,7 @@
"""
.trimIndent()
)
- runTest(listOf(mySource)) { invocation ->
+ runTest(listOf(mySource), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
val element = invocation.processingEnv.requireTypeElement("Subject")
element.getAnnotation(MainAnnotation::class)!!.let { annotation ->
assertThat(annotation.getAsTypeList("typeList").map { it.asTypeName() })
@@ -420,7 +430,8 @@
"""
.trimIndent()
)
- runTest(sources = listOf(kotlinSrc, javaSrc)) { invocation ->
+ runTest(sources = listOf(kotlinSrc, javaSrc), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) {
+ invocation ->
listOf("KotlinClass", "JavaClass")
.map { invocation.processingEnv.requireTypeElement(it) }
.forEach { typeElement ->
@@ -631,7 +642,8 @@
"""
.trimIndent()
)
- runTest(sources = listOf(javaSrc, kotlinSrc)) { invocation ->
+ runTest(sources = listOf(javaSrc, kotlinSrc), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) {
+ invocation ->
listOf("JavaSubject", "KotlinSubject")
.map(invocation.processingEnv::requireTypeElement)
.forEach { subject ->
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
index e8f2e14..27be10a 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
@@ -32,6 +32,7 @@
import androidx.room.compiler.processing.testcode.RepeatableJavaAnnotation
import androidx.room.compiler.processing.testcode.RepeatableKotlinAnnotation
import androidx.room.compiler.processing.testcode.TestSuppressWarnings
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.asJTypeName
@@ -55,7 +56,11 @@
@RunWith(Parameterized::class)
class XAnnotationTest(private val preCompiled: Boolean) {
- private fun runTest(sources: List<Source>, handler: (XTestInvocation) -> Unit) {
+ private fun runTest(
+ sources: List<Source>,
+ kotlincArgs: List<String> = emptyList(),
+ handler: (XTestInvocation) -> Unit
+ ) {
if (preCompiled) {
val compiled = compileFiles(sources)
val hasKotlinSources = sources.any { it is Source.KotlinSource }
@@ -68,9 +73,14 @@
val newSources =
kotlinSources +
Source.java("PlaceholderJava", "public class " + "PlaceholderJava {}")
- runProcessorTest(sources = newSources, handler = handler, classpath = compiled)
+ runProcessorTest(
+ sources = newSources,
+ handler = handler,
+ classpath = compiled,
+ kotlincArguments = kotlincArgs
+ )
} else {
- runProcessorTest(sources = sources, handler = handler)
+ runProcessorTest(sources = sources, handler = handler, kotlincArguments = kotlincArgs)
}
}
@@ -158,7 +168,8 @@
"""
.trimIndent()
)
- runTest(sources = listOf(javaSrc, kotlinSrc)) { invocation ->
+ runTest(sources = listOf(javaSrc, kotlinSrc), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) {
+ invocation ->
val typeElement = invocation.processingEnv.requireTypeElement("Foo")
val annotation =
typeElement.getAllAnnotations().single { it.qualifiedName == "MyAnnotation" }
@@ -464,7 +475,7 @@
"""
.trimIndent()
)
- runTest(listOf(mySource)) { invocation ->
+ runTest(listOf(mySource), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
val element = invocation.processingEnv.requireTypeElement("Subject")
val annotation = element.requireAnnotation<MainAnnotation>()
@@ -936,7 +947,8 @@
"""
.trimIndent()
)
- runTest(sources = listOf(javaSrc, kotlinSrc)) { invocation ->
+ runTest(sources = listOf(javaSrc, kotlinSrc), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) {
+ invocation ->
listOf("JavaSubject", "KotlinSubject")
.map(invocation.processingEnv::requireTypeElement)
.forEach { subject ->
@@ -1192,6 +1204,7 @@
.trimIndent()
)
),
+ kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
) { invocation ->
val subject = invocation.processingEnv.requireTypeElement("test.Subject")
val myAnnotation = invocation.processingEnv.requireTypeElement("test.MyAnnotation")
@@ -1324,7 +1337,8 @@
)
listOf(javaSource, kotlinSource).forEach { source ->
- runTest(sources = listOf(source)) { invocation ->
+ runTest(sources = listOf(source), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation
+ ->
fun XAnnotated.getAllAnnotationTypeElements(): List<XTypeElement> {
return getAllAnnotations()
.filter {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
index bc1a9a0..fc1bb0a 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
@@ -18,6 +18,7 @@
import androidx.kruth.assertThat
import androidx.room.compiler.codegen.JArrayTypeName
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.asJClassName
@@ -62,6 +63,7 @@
private fun runTest(
javaSource: Source.JavaSource,
kotlinSource: Source.KotlinSource,
+ kotlincArgs: List<String> = emptyList(),
handler: (XTestInvocation) -> Unit
) {
val sources =
@@ -81,9 +83,14 @@
val newSources =
kotlinSources +
Source.java("PlaceholderJava", "public class " + "PlaceholderJava {}")
- runProcessorTest(sources = newSources, handler = handler, classpath = compiled)
+ runProcessorTest(
+ sources = newSources,
+ handler = handler,
+ classpath = compiled,
+ kotlincArguments = kotlincArgs
+ )
} else {
- runProcessorTest(sources = sources, handler = handler)
+ runProcessorTest(sources = sources, handler = handler, kotlincArguments = kotlincArgs)
}
}
@@ -1330,7 +1337,8 @@
MyInterface
"""
.trimIndent()
- ) as Source.KotlinSource
+ ) as Source.KotlinSource,
+ kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
) { invocation ->
val classJTypeName =
JParameterizedTypeName.get(
@@ -1451,20 +1459,33 @@
@MyAnnotation(stringParam = "2") MyInterface
"""
.trimIndent()
- ) as Source.KotlinSource
+ ) as Source.KotlinSource,
+ kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
) { invocation ->
val annotation = getAnnotation(invocation)
// Compare the AnnotationSpec string ignoring whitespace
assertThat(annotation.toAnnotationSpec().toString().removeWhiteSpace())
.isEqualTo(
- """
+ // TODO(b/314151707): List values are missing in type annotations with K2. File
+ // a bug!
+ if (sourceKind == SourceKind.KOTLIN && isTypeAnnotation && isPreCompiled) {
+ """
+ @test.MyAnnotation(
+ stringParam="2",
+ stringParam2="1"
+ )
+ """
+ .removeWhiteSpace()
+ } else {
+ """
@test.MyAnnotation(
stringParam="2",
stringParam2="1",
stringArrayParam={"3","5","7"}
)
"""
- .removeWhiteSpace()
+ .removeWhiteSpace()
+ }
)
assertThat(
@@ -1488,7 +1509,15 @@
.firstOrNull()
?.value
)
- .isEqualTo("3")
+ .isEqualTo(
+ // TODO(b/314151707): List values are missing in type annotations with K2. File
+ // a bug!
+ if (sourceKind == SourceKind.KOTLIN && isTypeAnnotation && isPreCompiled) {
+ null
+ } else {
+ "3"
+ }
+ )
}
}
@@ -1554,7 +1583,8 @@
MyInterface
"""
.trimIndent()
- ) as Source.KotlinSource
+ ) as Source.KotlinSource,
+ kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
) { invocation ->
val aJTypeName = JClassName.get("test", "A")
val aKTypeName = KClassName("test", "A")
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
index b736a07..15781b1 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
@@ -26,6 +26,7 @@
import androidx.room.compiler.processing.ksp.KspFileMemberContainer
import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticFileMemberContainer
import androidx.room.compiler.processing.testcode.OtherAnnotation
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.asJClassName
@@ -866,7 +867,8 @@
"""
.trimIndent()
)
- )
+ ),
+ kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
) { invocation, precompiled ->
val enclosingElement =
invocation.processingEnv.requireTypeElement("foo.bar.KotlinClass")
@@ -1367,9 +1369,12 @@
private fun runProcessorTestHelper(
sources: List<Source>,
+ kotlincArgs: List<String> = emptyList(),
handler: (XTestInvocation, Boolean) -> Unit
) {
- runProcessorTest(sources = sources) { handler(it, false) }
- runProcessorTest(classpath = compileFiles(sources)) { handler(it, true) }
+ runProcessorTest(sources = sources, kotlincArguments = kotlincArgs) { handler(it, false) }
+ runProcessorTest(classpath = compileFiles(sources), kotlincArguments = kotlincArgs) {
+ handler(it, true)
+ }
}
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
index 1312ac5..3166c55 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
@@ -21,6 +21,7 @@
import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.codegen.asClassName
import androidx.room.compiler.processing.util.CONTINUATION_JCLASS_NAME
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.UNIT_JCLASS_NAME
import androidx.room.compiler.processing.util.className
@@ -505,7 +506,8 @@
"""
.trimIndent()
)
- runProcessorTest(sources = listOf(src)) { invocation ->
+ runProcessorTest(sources = listOf(src), kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS) {
+ invocation ->
val klass = invocation.processingEnv.requireTypeElement("MyDataClass")
val methodNames = klass.getAllMethods().map { it.jvmName }.toList()
assertThat(methodNames)
@@ -1346,7 +1348,11 @@
val sources = buildSources("app")
val classpath = compileFiles(buildSources("lib"))
- runProcessorTest(sources = sources, classpath = classpath) { invocation ->
+ runProcessorTest(
+ sources = sources,
+ classpath = classpath,
+ kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
+ ) { invocation ->
// we use this to remove the hash added by the compiler for function names that don't
// have valid JVM names
// regex: match 7 characters after -
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
index be06f3c..eef3af7 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
@@ -21,6 +21,7 @@
import androidx.room.compiler.codegen.asMutableClassName
import androidx.room.compiler.processing.ksp.KspProcessingEnv
import androidx.room.compiler.processing.util.CONTINUATION_JCLASS_NAME
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.UNIT_JCLASS_NAME
import androidx.room.compiler.processing.util.XTestInvocation
@@ -61,7 +62,8 @@
"""
.trimIndent()
)
- )
+ ),
+ kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
) { invocation ->
fun checkConstructor(className: String) {
val typeElement = invocation.processingEnv.requireTypeElement(className)
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XMessagerTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XMessagerTest.kt
index 6e65c40..113d34ab 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XMessagerTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XMessagerTest.kt
@@ -17,6 +17,7 @@
package androidx.room.compiler.processing
import androidx.kruth.assertThat
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.runProcessorTest
import javax.tools.Diagnostic
@@ -193,7 +194,8 @@
"""
.trimIndent()
)
- )
+ ),
+ kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
) {
val fooElement = it.processingEnv.requireTypeElement("test.Foo")
val fooAnnotations =
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
index ce9c840..8544d232 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
@@ -23,6 +23,7 @@
import androidx.room.compiler.codegen.asClassName
import androidx.room.compiler.processing.javac.JavacType
import androidx.room.compiler.processing.ksp.KspProcessingEnv
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.asKClassName
@@ -53,7 +54,11 @@
class XTypeElementTest(
private val isPreCompiled: Boolean,
) {
- private fun runTest(sources: List<Source>, handler: (XTestInvocation) -> Unit) {
+ private fun runTest(
+ sources: List<Source>,
+ kotlincArgs: List<String> = emptyList(),
+ handler: (XTestInvocation) -> Unit
+ ) {
if (isPreCompiled) {
val compiled = compileFiles(sources)
val hasKotlinSources = sources.any { it is Source.KotlinSource }
@@ -66,9 +71,14 @@
val newSources =
kotlinSources +
Source.java("PlaceholderJava", "public class " + "PlaceholderJava {}")
- runProcessorTest(sources = newSources, handler = handler, classpath = compiled)
+ runProcessorTest(
+ sources = newSources,
+ handler = handler,
+ classpath = compiled,
+ kotlincArguments = kotlincArgs
+ )
} else {
- runProcessorTest(sources = sources, handler = handler)
+ runProcessorTest(sources = sources, handler = handler, kotlincArguments = kotlincArgs)
}
}
@@ -1348,6 +1358,7 @@
.trimIndent()
)
),
+ kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
) { invocation ->
val appSubject = invocation.processingEnv.requireTypeElement("test.Subject")
val methodNames = appSubject.getAllMethods().map { it.name }.toList()
@@ -1614,7 +1625,7 @@
"""
.trimIndent()
)
- runTest(sources = listOf(src)) { invocation ->
+ runTest(sources = listOf(src), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
val defaultArgsConstructors =
invocation.processingEnv
.requireTypeElement("DefaultArgs")
@@ -1648,24 +1659,45 @@
)
.inOrder()
} else {
- assertThat(defaultArgsConstructors)
- .containsExactly(
- "DefaultArgs(int,double,long)",
- "DefaultArgs(double)",
- "DefaultArgs(int,double)"
- )
- .inOrder()
+ if (invocation.isKsp) {
+ assertThat(defaultArgsConstructors)
+ .containsExactly(
+ "DefaultArgs(int,double,long)",
+ "DefaultArgs(double)",
+ "DefaultArgs(int,double)",
+ )
+ .inOrder()
+ } else {
+ assertThat(defaultArgsConstructors)
+ .containsExactly(
+ "DefaultArgs(double)",
+ "DefaultArgs(int,double)",
+ "DefaultArgs(int,double,long)",
+ )
+ .inOrder()
+ }
assertThat(noDefaultArgsConstructors)
.containsExactly("NoDefaultArgs(int,double,long)")
.inOrder()
- assertThat(allDefaultArgsConstructors)
- .containsExactly(
- "AllDefaultArgs(int,double,long)",
- "AllDefaultArgs()",
- "AllDefaultArgs(int)",
- "AllDefaultArgs(int,double)"
- )
- .inOrder()
+ if (invocation.isKsp) {
+ assertThat(allDefaultArgsConstructors)
+ .containsExactly(
+ "AllDefaultArgs(int,double,long)",
+ "AllDefaultArgs()",
+ "AllDefaultArgs(int)",
+ "AllDefaultArgs(int,double)",
+ )
+ .inOrder()
+ } else {
+ assertThat(allDefaultArgsConstructors)
+ .containsExactly(
+ "AllDefaultArgs()",
+ "AllDefaultArgs(int)",
+ "AllDefaultArgs(int,double)",
+ "AllDefaultArgs(int,double,long)",
+ )
+ .inOrder()
+ }
}
val subjects = listOf("DefaultArgs", "NoDefaultArgs", "AllDefaultArgs")
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
index c3172f6..840c0e7 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
@@ -27,6 +27,7 @@
import androidx.room.compiler.processing.ksp.ERROR_JTYPE_NAME
import androidx.room.compiler.processing.ksp.ERROR_KTYPE_NAME
import androidx.room.compiler.processing.ksp.KspTypeArgumentType
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.asJClassName
@@ -1084,7 +1085,10 @@
"""
.trimIndent()
)
- runProcessorTest(sources = listOf(src, javaSource)) { invocation ->
+ runProcessorTest(
+ sources = listOf(src, javaSource),
+ kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
+ ) { invocation ->
val styleApplier = invocation.processingEnv.requireType("StyleApplier")
val styleBuilder = invocation.processingEnv.requireType("StyleBuilder")
assertThat(styleApplier.typeName.dumpToString(5))
@@ -1618,7 +1622,8 @@
.trimIndent()
)
),
- createProcessingSteps = { listOf(WildcardProcessingStep()) }
+ createProcessingSteps = { listOf(WildcardProcessingStep()) },
+ kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
) { result ->
result.hasError()
result.hasErrorCount(1)
@@ -2326,7 +2331,8 @@
compileFiles(listOf(kotlinSrc, javaSrc))
} else {
emptyList()
- }
+ },
+ kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
) { invocation ->
val kotlinElm = invocation.processingEnv.requireTypeElement("KotlinClass")
kotlinElm.getMethodByJvmName("kotlinValueClassDirectUsage").apply {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt
index db986f6..11f0abf 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt
@@ -26,6 +26,7 @@
import androidx.room.compiler.processing.ksp.KspProcessingEnv
import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticPropertyMethodElement
import androidx.room.compiler.processing.testcode.TestSuppressWarnings
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.getDeclaredField
@@ -34,7 +35,6 @@
import androidx.room.compiler.processing.util.runProcessorTest
import com.google.auto.common.MoreElements
import com.google.auto.common.MoreTypes
-import com.google.devtools.ksp.common.impl.KSNameImpl
import com.google.devtools.ksp.getDeclaredFunctions
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.JavaFile
@@ -562,7 +562,10 @@
@Test
fun annotationValues() {
- runProcessorTest(sources = listOf(kotlinSrc, javaSrc)) { invocation ->
+ runProcessorTest(
+ sources = listOf(kotlinSrc, javaSrc),
+ kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
+ ) { invocation ->
val kotlinClass = invocation.processingEnv.requireTypeElement("KotlinClass")
val javaClass = invocation.processingEnv.requireTypeElement("JavaClass")
@@ -858,7 +861,7 @@
(this.processingEnv as JavacProcessingEnv).delegate.elementUtils.getTypeElement(fqn)
private fun XTestInvocation.getKspTypeElement(fqn: String) =
- (this.processingEnv as KspProcessingEnv)
- .resolver
- .getClassDeclarationByName(KSNameImpl.getCached(fqn))!!
+ (this.processingEnv as KspProcessingEnv).resolver.let { resolver ->
+ resolver.getClassDeclarationByName(resolver.getKSNameFromString(fqn))
+ }!!
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
index 8aa8de5..f176019 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
@@ -20,6 +20,7 @@
import androidx.kruth.assertWithMessage
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.javac.JavacProcessingEnv
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compileFiles
@@ -818,11 +819,15 @@
"""
.trimIndent()
)
- simpleRun(sources = listOf(src)) { env ->
+ simpleRun(sources = listOf(src), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { env ->
val subject = env.requireTypeElement("Subject")
subject.getDeclaredFields().forEach { assertThat(it.getter).isNotNull() }
subject.getDeclaredMethods().forEach {
- assertThat(it.isKotlinPropertyMethod()).isTrue()
+ // A private static function was generated for the lambda passed to lazy() with K2
+ // so we filter these out.
+ if (!it.jvmName.contains("$")) {
+ assertThat(it.isKotlinPropertyMethod()).isTrue()
+ }
}
}
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt
index 6efe0df..d6ea9e1 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt
@@ -23,6 +23,7 @@
import androidx.room.compiler.processing.isField
import androidx.room.compiler.processing.isMethod
import androidx.room.compiler.processing.isTypeElement
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compileFiles
@@ -51,7 +52,7 @@
@Test
fun descriptor_method_simple() {
fun checkSources(vararg sources: Source) {
- runTest(sources = sources) { invocation ->
+ runTest(sources = sources, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
assertThat(invocation.annotatedElements().map(this::descriptor))
.containsExactly("emptyMethod()V")
}
@@ -82,8 +83,8 @@
@Test
fun descriptor_field() {
- fun checkSources(vararg sources: Source) {
- runTest(sources = sources) { invocation ->
+ fun checkSources(vararg sources: Source, kotlincArgs: List<String> = emptyList()) {
+ runTest(sources = sources, kotlincArgs = kotlincArgs) { invocation ->
assertThat(invocation.annotatedElements().map(this::descriptor))
.containsExactly(
"field1:I",
@@ -108,7 +109,8 @@
@Describe List<String> field4;
}
"""
- )
+ ),
+ kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
)
checkSources(
Source.kotlin(
@@ -129,7 +131,7 @@
@Test
fun descriptor_method_erasured() {
fun checkSources(vararg sources: Source) {
- runTest(sources = sources) { invocation ->
+ runTest(sources = sources, KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
assertThat(invocation.annotatedElements().map(this::descriptor))
.containsAtLeast(
"method1(Landroidx/room/test/Foo;)V",
@@ -204,7 +206,7 @@
@Test
fun descriptor_class_erasured() {
fun checkSources(vararg sources: Source) {
- runTest(sources = sources) { invocation ->
+ runTest(sources = sources, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
assertThat(invocation.annotatedElements().map(this::descriptor))
.containsExactly(
"method1(Ljava/lang/Object;)Ljava/lang/Object;",
@@ -277,7 +279,7 @@
@Test
fun descriptor_method_primitiveParams() {
fun checkSources(vararg sources: Source) {
- runTest(sources = sources) { invocation ->
+ runTest(sources = sources, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
assertThat(invocation.annotatedElements().map(this::descriptor))
.containsExactly("method1(ZI)V", "method2(C)B", "method3(DF)V", "method4(JS)V")
}
@@ -315,7 +317,7 @@
@Test
fun descriptor_method_classParam_javaTypes() {
fun checkSources(vararg sources: Source) {
- runTest(sources = sources) { invocation ->
+ runTest(sources = sources, KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
assertThat(invocation.annotatedElements().map(this::descriptor))
.containsExactly(
"method1(Ljava/lang/Object;)V",
@@ -363,7 +365,7 @@
@Test
fun descriptor_method_classParam_testClass() {
fun checkSources(vararg sources: Source) {
- runTest(sources = sources) { invocation ->
+ runTest(sources = sources, KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
assertThat(invocation.annotatedElements().map(this::descriptor))
.containsExactly(
"method1(Landroidx/room/test/DataClass;)V",
@@ -408,7 +410,7 @@
@Test
fun descriptor_method_classParam_innerTestClass() {
fun checkSources(vararg sources: Source) {
- runTest(sources = sources) { invocation ->
+ runTest(sources = sources, KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
assertThat(invocation.annotatedElements().map(this::descriptor))
.containsExactly(
"method1(Landroidx/room/test/DataClass\$MemberInnerData;)V",
@@ -467,7 +469,7 @@
@Test
fun descriptor_method_arrayParams() {
fun checkSources(vararg sources: Source) {
- runTest(sources = sources) { invocation ->
+ runTest(sources = sources, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
assertThat(invocation.annotatedElements().map(this::descriptor))
.containsExactly(
"method1([Landroidx/room/test/DataClass;)V",
@@ -515,7 +517,11 @@
)
}
- private fun runTest(vararg sources: Source, handler: (XTestInvocation) -> Unit) {
+ private fun runTest(
+ vararg sources: Source,
+ kotlincArgs: List<String> = emptyList(),
+ handler: (XTestInvocation) -> Unit
+ ) {
if (isPreCompiled) {
val compiled = compileFiles(listOf(*sources) + describeAnnotation)
val hasKotlinSources = sources.any { it is Source.KotlinSource }
@@ -528,9 +534,18 @@
val newSources =
kotlinSources +
Source.java("PlaceholderJava", "public class " + "PlaceholderJava {}")
- runProcessorTest(sources = newSources, handler = handler, classpath = compiled)
+ runProcessorTest(
+ sources = newSources,
+ handler = handler,
+ classpath = compiled,
+ kotlincArguments = kotlincArgs
+ )
} else {
- runProcessorTest(sources = listOf(*sources) + describeAnnotation, handler = handler)
+ runProcessorTest(
+ sources = listOf(*sources) + describeAnnotation,
+ handler = handler,
+ kotlincArguments = kotlincArgs
+ )
}
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
index 3b8cc6f..98645e6 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
@@ -21,6 +21,7 @@
import androidx.room.compiler.processing.XExecutableElement
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.util.CompilationTestCapabilities
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compileFiles
@@ -103,7 +104,11 @@
collectSignaturesInto(invocation, golden)
}
val ksp = mutableMapOf<String, List<TypeName>>()
- runKspTest(sources = sources, classpath = classpath) { invocation ->
+ runKspTest(
+ sources = sources,
+ classpath = classpath,
+ kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
+ ) { invocation ->
collectSignaturesInto(invocation, ksp)
}
diff --git a/room/room-compiler/build.gradle b/room/room-compiler/build.gradle
index 136bd39..ff0e1d5 100644
--- a/room/room-compiler/build.gradle
+++ b/room/room-compiler/build.gradle
@@ -87,7 +87,6 @@
testImplementation(libs.jsr250)
testImplementation(libs.mockitoCore4)
testImplementation(libs.antlr4)
- testImplementation(libs.kotlinCompilerEmbeddable)
testImplementation(SdkHelperKt.getSdkDependency(project))
testImplementationAarAsJar(project(":room:room-runtime"))
testImplementationAarAsJar(project(":sqlite:sqlite"))
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
index f57ed83..c73de92 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
@@ -222,20 +222,39 @@
}
object RoomRxJava2TypeNames {
- val RX_ROOM = XClassName.get(ROOM_PACKAGE, "RxRoom")
- val RX_ROOM_CREATE_FLOWABLE = "createFlowable"
- val RX_ROOM_CREATE_OBSERVABLE = "createObservable"
- val RX_EMPTY_RESULT_SET_EXCEPTION = XClassName.get(ROOM_PACKAGE, "EmptyResultSetException")
+ val RX2_ROOM = XClassName.get(ROOM_PACKAGE, "RxRoom")
+ val RX2_EMPTY_RESULT_SET_EXCEPTION = XClassName.get(ROOM_PACKAGE, "EmptyResultSetException")
+}
+
+object RoomRxJava2MemberNames {
+ val RX_ROOM_CREATE_FLOWABLE =
+ RoomRxJava2TypeNames.RX2_ROOM.companionMember("createFlowable", isJvmStatic = true)
+ val RX_ROOM_CREATE_OBSERVABLE =
+ RoomRxJava2TypeNames.RX2_ROOM.companionMember("createObservable", isJvmStatic = true)
+ val RX_ROOM_CREATE_SINGLE =
+ RoomRxJava2TypeNames.RX2_ROOM.companionMember("createSingle", isJvmStatic = true)
+ val RX_ROOM_CREATE_MAYBE =
+ RoomRxJava2TypeNames.RX2_ROOM.companionMember("createMaybe", isJvmStatic = true)
+ val RX_ROOM_CREATE_COMPLETABLE =
+ RoomRxJava2TypeNames.RX2_ROOM.companionMember("createCompletable", isJvmStatic = true)
}
object RoomRxJava3TypeNames {
- val RX_ROOM = XClassName.get("$ROOM_PACKAGE.rxjava3", "RxRoom")
- val RX_ROOM_CREATE_FLOWABLE = "createFlowable"
- val RX_ROOM_CREATE_OBSERVABLE = "createObservable"
- val RX_EMPTY_RESULT_SET_EXCEPTION =
+ val RX3_ROOM = XClassName.get("$ROOM_PACKAGE.rxjava3", "RxRoom")
+ val RX3_ROOM_MARKER = XClassName.get("$ROOM_PACKAGE.rxjava3", "Rx3RoomArtifactMarker")
+ val RX3_EMPTY_RESULT_SET_EXCEPTION =
XClassName.get("$ROOM_PACKAGE.rxjava3", "EmptyResultSetException")
}
+object RoomRxJava3MemberNames {
+ val RX_ROOM_CREATE_FLOWABLE = RoomRxJava3TypeNames.RX3_ROOM.packageMember("createFlowable")
+ val RX_ROOM_CREATE_OBSERVABLE = RoomRxJava3TypeNames.RX3_ROOM.packageMember("createObservable")
+ val RX_ROOM_CREATE_SINGLE = RoomRxJava3TypeNames.RX3_ROOM.packageMember("createSingle")
+ val RX_ROOM_CREATE_MAYBE = RoomRxJava3TypeNames.RX3_ROOM.packageMember("createMaybe")
+ val RX_ROOM_CREATE_COMPLETABLE =
+ RoomRxJava3TypeNames.RX3_ROOM.packageMember("createCompletable")
+}
+
object RoomPagingTypeNames {
val LIMIT_OFFSET_PAGING_SOURCE =
XClassName.get("$ROOM_PACKAGE.paging", "LimitOffsetPagingSource")
@@ -391,29 +410,56 @@
.build()
/**
+ * Short-hand of [InvokeWithLambdaParameter] whose function call is a member function, i.e. a
+ * top-level function or a companion object function.
+ */
+fun InvokeWithLambdaParameter(
+ scope: CodeGenScope,
+ functionName: XMemberName,
+ argFormat: List<String>,
+ args: List<Any>,
+ continuationParamName: String? = null,
+ lambdaSpec: LambdaSpec
+): XCodeBlock {
+ val functionCall = XCodeBlock.of(scope.language, "%M", functionName)
+ return InvokeWithLambdaParameter(
+ scope,
+ functionCall,
+ argFormat,
+ args,
+ continuationParamName,
+ lambdaSpec
+ )
+}
+
+/**
* Generates a code block that invokes a function with a functional type as last parameter.
*
* For Java (jvmTarget >= 8) it will generate:
* ```
- * <functionName>(<args>, (<lambdaSpec.paramName>) -> <lambdaSpec.body>);
+ * <functionCall>(<args>, (<lambdaSpec.paramName>) -> <lambdaSpec.body>);
* ```
*
* For Java (jvmTarget < 8) it will generate:
* ```
- * <functionName>(<args>, new Function1<>() { <lambdaSpec.body> });
+ * <functionCall>(<args>, new Function1<>() { <lambdaSpec.body> });
* ```
*
* For Kotlin it will generate:
* ```
- * <functionName>(<args>) { <lambdaSpec.body> }
+ * <functionCall>(<args>) { <lambdaSpec.body> }
* ```
*
+ * The [functionCall] must only be an expression up to a function name without the parenthesis. Its
+ * last parameter must also be a functional type. The [argFormat] and [args] are for the arguments
+ * of the function excluding the functional parameter.
+ *
* The ideal usage of this utility function is to generate code that invokes the various
* `DBUtil.perform*()` APIs for interacting with the database connection in DAOs.
*/
fun InvokeWithLambdaParameter(
scope: CodeGenScope,
- functionName: XMemberName,
+ functionCall: XCodeBlock,
argFormat: List<String>,
args: List<Any>,
continuationParamName: String? = null,
@@ -427,8 +473,8 @@
if (lambdaSpec.javaLambdaSyntaxAvailable) {
val argsFormatString = argFormat.joinToString(separator = ", ")
add(
- "%M($argsFormatString, (%L) -> {\n",
- functionName,
+ "%L($argsFormatString, (%L) -> {\n",
+ functionCall,
*args.toTypedArray(),
lambdaSpec.parameterName
)
@@ -472,8 +518,8 @@
}
}
add(
- "%M($adjustedArgsFormatString);\n",
- functionName,
+ "%L($adjustedArgsFormatString);\n",
+ functionCall,
*adjustedArgs.toTypedArray(),
)
}
@@ -482,15 +528,15 @@
val argsFormatString = argFormat.joinToString(separator = ", ")
if (lambdaSpec.parameterTypeName.rawTypeName != KotlinTypeNames.CONTINUATION) {
add(
- "%M($argsFormatString) { %L ->\n",
- functionName,
+ "%L($argsFormatString) { %L ->\n",
+ functionCall,
*args.toTypedArray(),
lambdaSpec.parameterName
)
} else {
add(
- "%M($argsFormatString) {\n",
- functionName,
+ "%L($argsFormatString) {\n",
+ functionCall,
*args.toTypedArray(),
)
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
index 8d375ac..1f2433c 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
@@ -20,8 +20,6 @@
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.processing.XElement
import androidx.room.compiler.processing.XProcessingEnv
-import androidx.room.compiler.processing.XType
-import androidx.room.ext.CommonTypeNames
import androidx.room.log.RLog
import androidx.room.parser.expansion.ProjectionExpander
import androidx.room.parser.optimization.RemoveUnusedColumnQueryRewriter
@@ -42,7 +40,6 @@
private val canRewriteQueriesToDropUnusedColumns: Boolean,
) {
val checker: Checks = Checks(logger)
- val COMMON_TYPES = CommonTypes(processingEnv)
/**
* Checks whether we should use the TypeConverter store that has a specific heuristic for
@@ -131,16 +128,6 @@
canRewriteQueriesToDropUnusedColumns = false
)
- class CommonTypes(val processingEnv: XProcessingEnv) {
- val VOID: XType by lazy { processingEnv.requireType(CommonTypeNames.VOID) }
- val STRING: XType by lazy { processingEnv.requireType(CommonTypeNames.STRING) }
- val READONLY_COLLECTION: XType by lazy {
- processingEnv.requireType(CommonTypeNames.COLLECTION)
- }
- val LIST: XType by lazy { processingEnv.requireType(CommonTypeNames.LIST) }
- val SET: XType by lazy { processingEnv.requireType(CommonTypeNames.SET) }
- }
-
val schemaInFolderPath by lazy {
val internalInputFolder =
processingEnv.options[ProcessorOptions.INTERNAL_SCHEMA_INPUT_FOLDER.argName]
@@ -323,4 +310,10 @@
targetPlatforms.contains(XProcessingEnv.Platform.JVM) &&
this.processingEnv.findType("android.content.Context") != null
}
+
+ /** Check if the target platform is JVM. */
+ fun isJvmOnlyTarget(): Boolean {
+ val targetPlatforms = this.processingEnv.targetPlatforms
+ return targetPlatforms.size == 1 && targetPlatforms.contains(XProcessingEnv.Platform.JVM)
+ }
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
index 7f1f632..b49a6c2 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
@@ -546,8 +546,11 @@
private fun processConstructorObject(element: XTypeElement): XTypeElement? {
val annotation = element.getAnnotation(androidx.room.ConstructedBy::class)
if (annotation == null) {
+ // If no @ConstructedBy is present then validate target is JVM (including Android)
+ // since reflection is available in those platforms and a database constructor is not
+ // needed.
context.checker.check(
- predicate = context.isAndroidOnlyTarget(),
+ predicate = context.isJvmOnlyTarget(),
element = element,
errorMsg = ProcessorErrors.MISSING_CONSTRUCTED_BY_ANNOTATION
)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/RxTypes.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/RxTypes.kt
index 762028e..b6b1fd6 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/RxTypes.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/RxTypes.kt
@@ -17,7 +17,10 @@
package androidx.room.solver
import androidx.room.compiler.codegen.XClassName
+import androidx.room.compiler.codegen.XMemberName
+import androidx.room.ext.RoomRxJava2MemberNames
import androidx.room.ext.RoomRxJava2TypeNames
+import androidx.room.ext.RoomRxJava3MemberNames
import androidx.room.ext.RoomRxJava3TypeNames
import androidx.room.ext.RxJava2TypeNames
import androidx.room.ext.RxJava3TypeNames
@@ -26,54 +29,80 @@
internal enum class RxType(
val version: RxVersion,
val className: XClassName,
- val factoryMethodName: String? = null,
+ val factoryMethodName: XMemberName,
val canBeNull: Boolean = false
) {
// RxJava2 types
RX2_FLOWABLE(
version = RxVersion.TWO,
className = RxJava2TypeNames.FLOWABLE,
- factoryMethodName = RoomRxJava2TypeNames.RX_ROOM_CREATE_FLOWABLE
+ factoryMethodName = RoomRxJava2MemberNames.RX_ROOM_CREATE_FLOWABLE
),
RX2_OBSERVABLE(
version = RxVersion.TWO,
className = RxJava2TypeNames.OBSERVABLE,
- factoryMethodName = RoomRxJava2TypeNames.RX_ROOM_CREATE_OBSERVABLE
+ factoryMethodName = RoomRxJava2MemberNames.RX_ROOM_CREATE_OBSERVABLE
),
- RX2_SINGLE(version = RxVersion.TWO, className = RxJava2TypeNames.SINGLE),
- RX2_MAYBE(version = RxVersion.TWO, className = RxJava2TypeNames.MAYBE, canBeNull = true),
- RX2_COMPLETABLE(version = RxVersion.TWO, className = RxJava2TypeNames.COMPLETABLE),
+ RX2_SINGLE(
+ version = RxVersion.TWO,
+ className = RxJava2TypeNames.SINGLE,
+ factoryMethodName = RoomRxJava2MemberNames.RX_ROOM_CREATE_SINGLE
+ ),
+ RX2_MAYBE(
+ version = RxVersion.TWO,
+ className = RxJava2TypeNames.MAYBE,
+ factoryMethodName = RoomRxJava2MemberNames.RX_ROOM_CREATE_MAYBE,
+ canBeNull = true
+ ),
+ RX2_COMPLETABLE(
+ version = RxVersion.TWO,
+ className = RxJava2TypeNames.COMPLETABLE,
+ factoryMethodName = RoomRxJava2MemberNames.RX_ROOM_CREATE_COMPLETABLE
+ ),
// RxJava3 types
RX3_FLOWABLE(
version = RxVersion.THREE,
className = RxJava3TypeNames.FLOWABLE,
- factoryMethodName = RoomRxJava3TypeNames.RX_ROOM_CREATE_FLOWABLE
+ factoryMethodName = RoomRxJava3MemberNames.RX_ROOM_CREATE_FLOWABLE
),
RX3_OBSERVABLE(
version = RxVersion.THREE,
className = RxJava3TypeNames.OBSERVABLE,
- factoryMethodName = RoomRxJava3TypeNames.RX_ROOM_CREATE_OBSERVABLE
+ factoryMethodName = RoomRxJava3MemberNames.RX_ROOM_CREATE_OBSERVABLE
),
- RX3_SINGLE(version = RxVersion.THREE, className = RxJava3TypeNames.SINGLE),
- RX3_MAYBE(version = RxVersion.THREE, className = RxJava3TypeNames.MAYBE, canBeNull = true),
- RX3_COMPLETABLE(version = RxVersion.THREE, className = RxJava3TypeNames.COMPLETABLE);
+ RX3_SINGLE(
+ version = RxVersion.THREE,
+ className = RxJava3TypeNames.SINGLE,
+ factoryMethodName = RoomRxJava3MemberNames.RX_ROOM_CREATE_SINGLE
+ ),
+ RX3_MAYBE(
+ version = RxVersion.THREE,
+ className = RxJava3TypeNames.MAYBE,
+ factoryMethodName = RoomRxJava3MemberNames.RX_ROOM_CREATE_MAYBE,
+ canBeNull = true
+ ),
+ RX3_COMPLETABLE(
+ version = RxVersion.THREE,
+ className = RxJava3TypeNames.COMPLETABLE,
+ factoryMethodName = RoomRxJava3MemberNames.RX_ROOM_CREATE_COMPLETABLE
+ );
fun isSingle() = this == RX2_SINGLE || this == RX3_SINGLE
}
internal enum class RxVersion(
- val rxRoomClassName: XClassName,
+ val rxMarkerClassName: XClassName,
val emptyResultExceptionClassName: XClassName,
val missingArtifactMessage: String
) {
TWO(
- rxRoomClassName = RoomRxJava2TypeNames.RX_ROOM,
- emptyResultExceptionClassName = RoomRxJava2TypeNames.RX_EMPTY_RESULT_SET_EXCEPTION,
+ rxMarkerClassName = RoomRxJava2TypeNames.RX2_ROOM,
+ emptyResultExceptionClassName = RoomRxJava2TypeNames.RX2_EMPTY_RESULT_SET_EXCEPTION,
missingArtifactMessage = ProcessorErrors.MISSING_ROOM_RXJAVA2_ARTIFACT
),
THREE(
- rxRoomClassName = RoomRxJava3TypeNames.RX_ROOM,
- emptyResultExceptionClassName = RoomRxJava3TypeNames.RX_EMPTY_RESULT_SET_EXCEPTION,
+ rxMarkerClassName = RoomRxJava3TypeNames.RX3_ROOM_MARKER,
+ emptyResultExceptionClassName = RoomRxJava3TypeNames.RX3_EMPTY_RESULT_SET_EXCEPTION,
missingArtifactMessage = ProcessorErrors.MISSING_ROOM_RXJAVA3_ARTIFACT
)
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
index 4523c50..b6d986f 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
@@ -54,9 +54,9 @@
import androidx.room.solver.binderprovider.ListenableFuturePagingSourceQueryResultBinderProvider
import androidx.room.solver.binderprovider.LiveDataQueryResultBinderProvider
import androidx.room.solver.binderprovider.PagingSourceQueryResultBinderProvider
-import androidx.room.solver.binderprovider.RxCallableQueryResultBinderProvider
import androidx.room.solver.binderprovider.RxJava2PagingSourceQueryResultBinderProvider
import androidx.room.solver.binderprovider.RxJava3PagingSourceQueryResultBinderProvider
+import androidx.room.solver.binderprovider.RxLambdaQueryResultBinderProvider
import androidx.room.solver.binderprovider.RxQueryResultBinderProvider
import androidx.room.solver.prepared.binder.PreparedQueryResultBinder
import androidx.room.solver.prepared.binderprovider.GuavaListenableFuturePreparedQueryResultBinderProvider
@@ -208,7 +208,7 @@
add(LiveDataQueryResultBinderProvider(context))
add(GuavaListenableFutureQueryResultBinderProvider(context))
addAll(RxQueryResultBinderProvider.getAll(context))
- addAll(RxCallableQueryResultBinderProvider.getAll(context))
+ addAll(RxLambdaQueryResultBinderProvider.getAll(context))
add(DataSourceQueryResultBinderProvider(context))
add(DataSourceFactoryQueryResultBinderProvider(context))
add(RxJava2PagingSourceQueryResultBinderProvider(context))
@@ -795,12 +795,13 @@
mapInfo: MapInfo?,
mapValueTypeArg: XType
): MapValueResultAdapter? {
- val collectionTypeRaw = context.COMMON_TYPES.READONLY_COLLECTION.rawType
+ val collectionTypeRaw =
+ context.processingEnv.requireType(CommonTypeNames.COLLECTION).rawType
if (collectionTypeRaw.isAssignableFrom(mapValueTypeArg.rawType)) {
// The Map's value type argument is assignable to a Collection, we need to make
// sure it is either a list or a set.
- val listTypeRaw = context.COMMON_TYPES.LIST.rawType
- val setTypeRaw = context.COMMON_TYPES.SET.rawType
+ val listTypeRaw = context.processingEnv.requireType(CommonTypeNames.LIST).rawType
+ val setTypeRaw = context.processingEnv.requireType(CommonTypeNames.SET).rawType
val collectionValueType =
when {
mapValueTypeArg.rawType.isAssignableFrom(listTypeRaw) ->
@@ -1021,7 +1022,8 @@
typeMirror: XType,
isMultipleParameter: Boolean
): QueryParameterAdapter? {
- if (context.COMMON_TYPES.READONLY_COLLECTION.rawType.isAssignableFrom(typeMirror)) {
+ val collectionType = context.processingEnv.requireType(CommonTypeNames.COLLECTION)
+ if (collectionType.rawType.isAssignableFrom(typeMirror)) {
val typeArg = typeMirror.typeArguments.first().extendsBoundOrSelf()
// An adapter for the collection type arg wrapped in the built-in collection adapter.
val wrappedCollectionAdapter =
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
index 3d1c20f..edd1307 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
@@ -20,6 +20,7 @@
import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.processing.XRawType
import androidx.room.compiler.processing.XType
+import androidx.room.ext.CommonTypeNames
import androidx.room.parser.ParsedQuery
import androidx.room.processor.Context
import androidx.room.processor.ProcessorErrors
@@ -64,7 +65,8 @@
}
override fun matches(declared: XType): Boolean {
- val collectionTypeRaw = context.COMMON_TYPES.READONLY_COLLECTION.rawType
+ val collectionTypeRaw =
+ context.processingEnv.requireType(CommonTypeNames.COLLECTION).rawType
if (pagingSourceType == null) {
return false
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxLambdaQueryResultBinderProvider.kt
similarity index 89%
rename from room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxLambdaQueryResultBinderProvider.kt
index 129241d..701cf87 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxLambdaQueryResultBinderProvider.kt
@@ -24,9 +24,9 @@
import androidx.room.solver.RxType
import androidx.room.solver.TypeAdapterExtras
import androidx.room.solver.query.result.QueryResultBinder
-import androidx.room.solver.query.result.RxCallableQueryResultBinder
+import androidx.room.solver.query.result.RxLambdaQueryResultBinder
-class RxCallableQueryResultBinderProvider
+class RxLambdaQueryResultBinderProvider
private constructor(val context: Context, private val rxType: RxType) : QueryResultBinderProvider {
override fun provide(
declared: XType,
@@ -40,7 +40,7 @@
)
val typeArg = extractTypeArg(declared)
val adapter = context.typeAdapterStore.findQueryResultAdapter(typeArg, query, extras)
- return RxCallableQueryResultBinder(rxType, typeArg, adapter)
+ return RxLambdaQueryResultBinder(rxType, typeArg, adapter)
}
override fun matches(declared: XType): Boolean =
@@ -62,10 +62,10 @@
companion object {
fun getAll(context: Context) =
listOf(RxType.RX2_SINGLE, RxType.RX2_MAYBE, RxType.RX3_SINGLE, RxType.RX3_MAYBE).map {
- RxCallableQueryResultBinderProvider(context, it)
+ RxLambdaQueryResultBinderProvider(context, it)
.requireArtifact(
context = context,
- requiredType = it.version.rxRoomClassName,
+ requiredType = it.version.rxMarkerClassName,
missingArtifactErrorMsg = it.version.missingArtifactMessage
)
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxQueryResultBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxQueryResultBinderProvider.kt
index a592faaf..aad54a6 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxQueryResultBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxQueryResultBinderProvider.kt
@@ -25,6 +25,7 @@
import androidx.room.solver.query.result.QueryResultBinder
import androidx.room.solver.query.result.RxQueryResultBinder
+/** Generic result binder for Rx classes that are reactive. */
class RxQueryResultBinderProvider
private constructor(context: Context, private val rxType: RxType) :
ObservableQueryResultBinderProvider(context) {
@@ -69,7 +70,7 @@
RxQueryResultBinderProvider(context, it)
.requireArtifact(
context = context,
- requiredType = it.version.rxRoomClassName,
+ requiredType = it.version.rxMarkerClassName,
missingArtifactErrorMsg = it.version.missingArtifactMessage
)
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/CallablePreparedQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/CallablePreparedQueryResultBinder.kt
deleted file mode 100644
index 622bb16..0000000
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/CallablePreparedQueryResultBinder.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room.solver.prepared.binder
-
-import androidx.room.compiler.codegen.XCodeBlock
-import androidx.room.compiler.codegen.XPropertySpec
-import androidx.room.compiler.codegen.XTypeSpec
-import androidx.room.compiler.processing.XType
-import androidx.room.ext.CallableTypeSpecBuilder
-import androidx.room.solver.CodeGenScope
-import androidx.room.solver.prepared.result.PreparedQueryResultAdapter
-
-/**
- * Binder for deferred queries.
- *
- * This binder will create a Callable implementation that delegates to the
- * [PreparedQueryResultAdapter]. Usage of the Callable impl is then delegate to the [addStmntBlock]
- * function.
- */
-class CallablePreparedQueryResultBinder
-private constructor(
- val returnType: XType,
- val addStmntBlock:
- XCodeBlock.Builder.(callableImpl: XTypeSpec, dbProperty: XPropertySpec) -> Unit,
- adapter: PreparedQueryResultAdapter?
-) : PreparedQueryResultBinder(adapter) {
-
- companion object {
- fun createPreparedBinder(
- returnType: XType,
- adapter: PreparedQueryResultAdapter?,
- addCodeBlock:
- XCodeBlock.Builder.(callableImpl: XTypeSpec, dbProperty: XPropertySpec) -> Unit
- ) = CallablePreparedQueryResultBinder(returnType, addCodeBlock, adapter)
- }
-
- override fun executeAndReturn(
- prepareQueryStmtBlock: CodeGenScope.() -> String,
- preparedStmtProperty: XPropertySpec?,
- dbProperty: XPropertySpec,
- scope: CodeGenScope
- ) {
- val binderScope = scope.fork()
- val callableImpl =
- CallableTypeSpecBuilder(scope.language, returnType.asTypeName()) {
- adapter?.executeAndReturn(
- binderScope.prepareQueryStmtBlock(),
- preparedStmtProperty,
- dbProperty,
- binderScope
- )
- addCode(binderScope.generate())
- }
- .build()
-
- scope.builder.apply { addStmntBlock(callableImpl, dbProperty) }
- }
-}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/RxPreparedQueryResultBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/RxPreparedQueryResultBinderProvider.kt
index 5b5a248..d7153fc 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/RxPreparedQueryResultBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/RxPreparedQueryResultBinderProvider.kt
@@ -18,10 +18,11 @@
import androidx.room.compiler.processing.XRawType
import androidx.room.compiler.processing.XType
+import androidx.room.ext.KotlinTypeNames
import androidx.room.parser.ParsedQuery
import androidx.room.processor.Context
import androidx.room.solver.RxType
-import androidx.room.solver.prepared.binder.CallablePreparedQueryResultBinder.Companion.createPreparedBinder
+import androidx.room.solver.prepared.binder.LambdaPreparedQueryResultBinder
import androidx.room.solver.prepared.binder.PreparedQueryResultBinder
open class RxPreparedQueryResultBinderProvider
@@ -29,7 +30,8 @@
PreparedQueryResultBinderProvider {
private val hasRxJavaArtifact by lazy {
- context.processingEnv.findTypeElement(rxType.version.rxRoomClassName.canonicalName) != null
+ context.processingEnv.findTypeElement(rxType.version.rxMarkerClassName.canonicalName) !=
+ null
}
override fun matches(declared: XType): Boolean =
@@ -44,12 +46,11 @@
context.logger.e(rxType.version.missingArtifactMessage)
}
val typeArg = extractTypeArg(declared)
- return createPreparedBinder(
+ return LambdaPreparedQueryResultBinder(
returnType = typeArg,
+ functionName = rxType.factoryMethodName,
adapter = context.typeAdapterStore.findPreparedQueryResultAdapter(typeArg, query)
- ) { callableImpl, _ ->
- addStatement("return %T.fromCallable(%L)", rxType.className, callableImpl)
- }
+ )
}
open fun extractTypeArg(declared: XType): XType = declared.typeArguments.first()
@@ -82,16 +83,17 @@
}
/**
- * Since Completable is not a generic, the supported return type should be Void (nullable). Like
- * this, the generated Callable.call method will return Void.
+ * Since Completable has no type argument, the supported return type is Unit (non-nullable)
+ * since the 'createCompletable" factory method take a Kotlin lambda.
*/
- override fun extractTypeArg(declared: XType): XType = context.COMMON_TYPES.VOID.makeNullable()
+ override fun extractTypeArg(declared: XType): XType =
+ context.processingEnv.requireType(KotlinTypeNames.UNIT)
}
private class RxSingleOrMaybePreparedQueryResultBinderProvider(context: Context, rxType: RxType) :
RxPreparedQueryResultBinderProvider(context, rxType) {
- /** Since Maybe can have null values, the Callable returned must allow for null values. */
+ /** Since Maybe can have null values, the lambda returned must allow for null values. */
override fun extractTypeArg(declared: XType): XType =
declared.typeArguments.first().makeNullable()
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
index 06d49c8..e4898ee 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
@@ -18,11 +18,16 @@
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.codegen.XCodeBlock
+import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.processing.XType
import androidx.room.ext.ArrayLiteral
import androidx.room.ext.CallableTypeSpecBuilder
import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
+import androidx.room.ext.SQLiteDriverTypeNames
import androidx.room.solver.CodeGenScope
/** Converts the query into a LiveData and returns it. No query is run until necessary. */
@@ -31,7 +36,6 @@
val tableNames: Set<String>,
adapter: QueryResultAdapter?
) : BaseObservableQueryResultBinder(adapter) {
- @Suppress("JoinDeclarationAndAssignment")
override fun convertAndReturn(
roomSQLiteQueryVar: String,
canReleaseQuery: Boolean,
@@ -83,4 +87,68 @@
)
}
}
+
+ override fun isMigratedToDriver() = adapter?.isMigratedToDriver() == true
+
+ override fun convertAndReturn(
+ sqlQueryVar: String,
+ dbProperty: XPropertySpec,
+ bindStatement: CodeGenScope.(String) -> Unit,
+ returnTypeName: XTypeName,
+ inTransaction: Boolean,
+ scope: CodeGenScope
+ ) {
+ val arrayOfTableNamesLiteral =
+ ArrayLiteral(scope.language, CommonTypeNames.STRING, *tableNames.toTypedArray())
+ val connectionVar = scope.getTmpVar("_connection")
+ val createBlock =
+ InvokeWithLambdaParameter(
+ scope = scope,
+ functionCall =
+ XCodeBlock.of(
+ scope.language,
+ "%N.%L.createLiveData",
+ dbProperty,
+ when (scope.language) {
+ CodeLanguage.JAVA -> "getInvalidationTracker()"
+ CodeLanguage.KOTLIN -> "invalidationTracker"
+ }
+ ),
+ argFormat = listOf("%L", "%L"),
+ args = listOf(arrayOfTableNamesLiteral, inTransaction),
+ lambdaSpec =
+ object :
+ LambdaSpec(
+ parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+ parameterName = connectionVar,
+ returnTypeName = typeArg.asTypeName(),
+ javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
+ ) {
+ override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+ val returnPrefix =
+ when (language) {
+ CodeLanguage.JAVA -> "return "
+ CodeLanguage.KOTLIN -> ""
+ }
+ val statementVar = scope.getTmpVar("_stmt")
+ addLocalVal(
+ statementVar,
+ SQLiteDriverTypeNames.STATEMENT,
+ "%L.prepare(%L)",
+ connectionVar,
+ sqlQueryVar
+ )
+ beginControlFlow("try")
+ bindStatement(scope, statementVar)
+ val outVar = scope.getTmpVar("_result")
+ adapter?.convert(outVar, statementVar, scope)
+ addStatement("$returnPrefix%L", outVar)
+ nextControlFlow("finally")
+ addStatement("%L.close()", statementVar)
+ endControlFlow()
+ }
+ }
+ )
+ scope.builder.add("return %L", createBlock)
+ }
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxCallableQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxLambdaQueryResultBinder.kt
similarity index 68%
rename from room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxCallableQueryResultBinder.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxLambdaQueryResultBinder.kt
index a16c91f..10973f37 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxCallableQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxLambdaQueryResultBinder.kt
@@ -19,20 +19,25 @@
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.codegen.VisibilityModifier
import androidx.room.compiler.codegen.XCodeBlock
+import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
import androidx.room.compiler.codegen.XFunSpec
import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.codegen.XTypeSpec
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.XType
import androidx.room.ext.AndroidTypeNames.CURSOR
import androidx.room.ext.CallableTypeSpecBuilder
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
import androidx.room.ext.RoomMemberNames.DB_UTIL_QUERY
+import androidx.room.ext.SQLiteDriverTypeNames
import androidx.room.solver.CodeGenScope
import androidx.room.solver.RxType
-/** Generic Result binder for Rx classes that accept a callable. */
-internal class RxCallableQueryResultBinder(
+/** Generic result binder for Rx classes that are not reactive. */
+internal class RxLambdaQueryResultBinder(
private val rxType: RxType,
val typeArg: XType,
adapter: QueryResultAdapter?
@@ -68,7 +73,7 @@
.build()
scope.builder.apply {
if (rxType.isSingle()) {
- addStatement("return %T.createSingle(%L)", rxType.version.rxRoomClassName, callable)
+ addStatement("return %M(%L)", rxType.factoryMethodName, callable)
} else {
addStatement("return %T.fromCallable(%L)", rxType.className, callable)
}
@@ -160,4 +165,57 @@
.build()
)
}
+
+ override fun isMigratedToDriver() = adapter?.isMigratedToDriver() == true
+
+ override fun convertAndReturn(
+ sqlQueryVar: String,
+ dbProperty: XPropertySpec,
+ bindStatement: CodeGenScope.(String) -> Unit,
+ returnTypeName: XTypeName,
+ inTransaction: Boolean,
+ scope: CodeGenScope
+ ) {
+ val connectionVar = scope.getTmpVar("_connection")
+ val performBlock =
+ InvokeWithLambdaParameter(
+ scope = scope,
+ functionName = rxType.factoryMethodName,
+ argFormat = listOf("%N", "%L", "%L"),
+ args = listOf(dbProperty, /* isReadOnly= */ true, inTransaction),
+ lambdaSpec =
+ object :
+ LambdaSpec(
+ parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+ parameterName = connectionVar,
+ returnTypeName = typeArg.asTypeName(),
+ javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
+ ) {
+ override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+ val returnPrefix =
+ when (language) {
+ CodeLanguage.JAVA -> "return "
+ CodeLanguage.KOTLIN -> ""
+ }
+ val statementVar = scope.getTmpVar("_stmt")
+ addLocalVal(
+ statementVar,
+ SQLiteDriverTypeNames.STATEMENT,
+ "%L.prepare(%L)",
+ connectionVar,
+ sqlQueryVar
+ )
+ beginControlFlow("try")
+ bindStatement(scope, statementVar)
+ val outVar = scope.getTmpVar("_result")
+ adapter?.convert(outVar, statementVar, scope)
+ addStatement("$returnPrefix%L", outVar)
+ nextControlFlow("finally")
+ addStatement("%L.close()", statementVar)
+ endControlFlow()
+ }
+ }
+ )
+ scope.builder.add("return %L", performBlock)
+ }
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
index 838c502..bc8877e 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
@@ -16,12 +16,18 @@
package androidx.room.solver.query.result
+import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.codegen.XCodeBlock
+import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.processing.XType
import androidx.room.ext.ArrayLiteral
import androidx.room.ext.CallableTypeSpecBuilder
import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
+import androidx.room.ext.SQLiteDriverTypeNames
import androidx.room.solver.CodeGenScope
import androidx.room.solver.RxType
@@ -69,9 +75,8 @@
*queryTableNames.toTypedArray()
)
addStatement(
- "return %T.%N(%N, %L, %L, %L)",
- rxType.version.rxRoomClassName,
- rxType.factoryMethodName!!,
+ "return %M(%N, %L, %L, %L)",
+ rxType.factoryMethodName,
dbProperty,
if (inTransaction) "true" else "false",
arrayOfTableNamesLiteral,
@@ -79,4 +84,66 @@
)
}
}
+
+ override fun isMigratedToDriver() = adapter?.isMigratedToDriver() ?: false
+
+ override fun convertAndReturn(
+ sqlQueryVar: String,
+ dbProperty: XPropertySpec,
+ bindStatement: CodeGenScope.(String) -> Unit,
+ returnTypeName: XTypeName,
+ inTransaction: Boolean,
+ scope: CodeGenScope
+ ) {
+ val connectionVar = scope.getTmpVar("_connection")
+ val performBlock =
+ InvokeWithLambdaParameter(
+ scope = scope,
+ functionName = rxType.factoryMethodName,
+ argFormat = listOf("%N", "%L", "%L"),
+ args =
+ listOf(
+ dbProperty,
+ inTransaction,
+ ArrayLiteral(
+ scope.language,
+ CommonTypeNames.STRING,
+ *queryTableNames.toTypedArray()
+ )
+ ),
+ lambdaSpec =
+ object :
+ LambdaSpec(
+ parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+ parameterName = connectionVar,
+ returnTypeName = typeArg.asTypeName(),
+ javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
+ ) {
+ override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+ val returnPrefix =
+ when (language) {
+ CodeLanguage.JAVA -> "return "
+ CodeLanguage.KOTLIN -> ""
+ }
+ val statementVar = scope.getTmpVar("_stmt")
+ addLocalVal(
+ statementVar,
+ SQLiteDriverTypeNames.STATEMENT,
+ "%L.prepare(%L)",
+ connectionVar,
+ sqlQueryVar
+ )
+ beginControlFlow("try")
+ bindStatement(scope, statementVar)
+ val outVar = scope.getTmpVar("_result")
+ adapter?.convert(outVar, statementVar, scope)
+ addStatement("$returnPrefix%L", outVar)
+ nextControlFlow("finally")
+ addStatement("%L.close()", statementVar)
+ endControlFlow()
+ }
+ }
+ )
+ scope.builder.add("return %L", performBlock)
+ }
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableDeleteOrUpdateMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableDeleteOrUpdateMethodBinder.kt
deleted file mode 100644
index 58f383f..0000000
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableDeleteOrUpdateMethodBinder.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room.solver.shortcut.binder
-
-import androidx.room.compiler.codegen.XCodeBlock
-import androidx.room.compiler.codegen.XPropertySpec
-import androidx.room.compiler.codegen.XTypeSpec
-import androidx.room.compiler.processing.XType
-import androidx.room.ext.CallableTypeSpecBuilder
-import androidx.room.solver.CodeGenScope
-import androidx.room.solver.shortcut.result.DeleteOrUpdateMethodAdapter
-import androidx.room.vo.ShortcutQueryParameter
-
-/**
- * Binder for deferred delete and update methods.
- *
- * This binder will create a Callable implementation that delegates to the
- * [DeleteOrUpdateMethodAdapter]. Usage of the Callable impl is then delegate to the [addStmntBlock]
- * function.
- */
-class CallableDeleteOrUpdateMethodBinder
-private constructor(
- val typeArg: XType,
- val addStmntBlock: XCodeBlock.Builder.(callableImpl: XTypeSpec, dbField: XPropertySpec) -> Unit,
- adapter: DeleteOrUpdateMethodAdapter?
-) : DeleteOrUpdateMethodBinder(adapter) {
-
- companion object {
- fun createDeleteOrUpdateBinder(
- typeArg: XType,
- adapter: DeleteOrUpdateMethodAdapter?,
- addCodeBlock:
- XCodeBlock.Builder.(callableImpl: XTypeSpec, dbField: XPropertySpec) -> Unit
- ) = CallableDeleteOrUpdateMethodBinder(typeArg, addCodeBlock, adapter)
- }
-
- override fun convertAndReturn(
- parameters: List<ShortcutQueryParameter>,
- adapters: Map<String, Pair<XPropertySpec, XTypeSpec>>,
- dbProperty: XPropertySpec,
- scope: CodeGenScope
- ) {
- convertAndReturnCompat(parameters, adapters, dbProperty, scope)
- }
-
- override fun convertAndReturnCompat(
- parameters: List<ShortcutQueryParameter>,
- adapters: Map<String, Pair<XPropertySpec, XTypeSpec>>,
- dbProperty: XPropertySpec,
- scope: CodeGenScope
- ) {
- val adapterScope = scope.fork()
- val callableImpl =
- CallableTypeSpecBuilder(scope.language, typeArg.asTypeName()) {
- adapter?.generateMethodBodyCompat(
- parameters = parameters,
- adapters = adapters,
- dbProperty = dbProperty,
- scope = adapterScope
- )
- addCode(adapterScope.generate())
- }
- .build()
-
- scope.builder.apply { addStmntBlock(callableImpl, dbProperty) }
- }
-}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableInsertOrUpsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableInsertOrUpsertMethodBinder.kt
deleted file mode 100644
index 7dee268..0000000
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableInsertOrUpsertMethodBinder.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room.solver.shortcut.binder
-
-import androidx.room.compiler.codegen.XCodeBlock
-import androidx.room.compiler.codegen.XPropertySpec
-import androidx.room.compiler.codegen.XTypeSpec
-import androidx.room.compiler.processing.XType
-import androidx.room.ext.CallableTypeSpecBuilder
-import androidx.room.solver.CodeGenScope
-import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
-import androidx.room.vo.ShortcutQueryParameter
-
-/**
- * Binder for deferred insert methods.
- *
- * This binder will create a Callable implementation that delegates to the
- * [InsertOrUpsertMethodAdapter]. Usage of the Callable impl is then delegate to the [addStmntBlock]
- * function.
- */
-class CallableInsertOrUpsertMethodBinder(
- val typeArg: XType,
- val addStmntBlock: XCodeBlock.Builder.(callableImpl: XTypeSpec, dbField: XPropertySpec) -> Unit,
- adapter: InsertOrUpsertMethodAdapter?
-) : InsertOrUpsertMethodBinder(adapter) {
-
- companion object {
- fun createInsertOrUpsertBinder(
- typeArg: XType,
- adapter: InsertOrUpsertMethodAdapter?,
- addCodeBlock:
- XCodeBlock.Builder.(callableImpl: XTypeSpec, dbField: XPropertySpec) -> Unit
- ) = CallableInsertOrUpsertMethodBinder(typeArg, addCodeBlock, adapter)
- }
-
- override fun convertAndReturn(
- parameters: List<ShortcutQueryParameter>,
- adapters: Map<String, Pair<XPropertySpec, Any>>,
- dbProperty: XPropertySpec,
- scope: CodeGenScope
- ) {
- convertAndReturnCompat(parameters, adapters, dbProperty, scope)
- }
-
- override fun convertAndReturnCompat(
- parameters: List<ShortcutQueryParameter>,
- adapters: Map<String, Pair<XPropertySpec, Any>>,
- dbProperty: XPropertySpec,
- scope: CodeGenScope
- ) {
- val adapterScope = scope.fork()
- val callableImpl =
- CallableTypeSpecBuilder(scope.language, typeArg.asTypeName()) {
- addCode(
- XCodeBlock.builder(language)
- .apply {
- adapter?.generateMethodBodyCompat(
- parameters = parameters,
- adapters = adapters,
- dbProperty = dbProperty,
- scope = adapterScope
- )
- addCode(adapterScope.generate())
- }
- .build()
- )
- }
- .build()
-
- scope.builder.apply { addStmntBlock(callableImpl, dbProperty) }
- }
-}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableDeleteOrUpdateMethodBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableDeleteOrUpdateMethodBinderProvider.kt
index fa50cc4..535e608 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableDeleteOrUpdateMethodBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableDeleteOrUpdateMethodBinderProvider.kt
@@ -18,10 +18,11 @@
import androidx.room.compiler.processing.XRawType
import androidx.room.compiler.processing.XType
+import androidx.room.ext.KotlinTypeNames
import androidx.room.processor.Context
import androidx.room.solver.RxType
-import androidx.room.solver.shortcut.binder.CallableDeleteOrUpdateMethodBinder.Companion.createDeleteOrUpdateBinder
import androidx.room.solver.shortcut.binder.DeleteOrUpdateMethodBinder
+import androidx.room.solver.shortcut.binder.LambdaDeleteOrUpdateMethodBinder
/** Provider for Rx Callable binders. */
open class RxCallableDeleteOrUpdateMethodBinderProvider
@@ -44,9 +45,11 @@
override fun provide(declared: XType): DeleteOrUpdateMethodBinder {
val typeArg = extractTypeArg(declared)
val adapter = context.typeAdapterStore.findDeleteOrUpdateAdapter(typeArg)
- return createDeleteOrUpdateBinder(typeArg, adapter) { callableImpl, _ ->
- addStatement("return %T.fromCallable(%L)", rxType.className, callableImpl)
- }
+ return LambdaDeleteOrUpdateMethodBinder(
+ typeArg = typeArg,
+ functionName = rxType.factoryMethodName,
+ adapter = adapter
+ )
}
companion object {
@@ -70,25 +73,24 @@
}
/**
- * Since Completable is not a generic, the supported return type should be Void (nullable). Like
- * this, the generated Callable.call method will return Void.
+ * Since Completable has no type argument, the supported return type is Unit (non-nullable)
+ * since the 'createCompletable" factory method take a Kotlin lambda.
*/
- override fun extractTypeArg(declared: XType): XType = context.COMMON_TYPES.VOID.makeNullable()
+ override fun extractTypeArg(declared: XType): XType =
+ context.processingEnv.requireType(KotlinTypeNames.UNIT)
override fun matches(declared: XType): Boolean = isCompletable(declared)
private fun isCompletable(declared: XType): Boolean {
- if (completableType == null) {
- return false
- }
- return declared.rawType.isAssignableFrom(completableType!!)
+ val completableType = this.completableType ?: return false
+ return declared.rawType.isAssignableFrom(completableType)
}
}
private class RxSingleOrMaybeDeleteOrUpdateMethodBinderProvider(context: Context, rxType: RxType) :
RxCallableDeleteOrUpdateMethodBinderProvider(context, rxType) {
- /** Since Maybe can have null values, the Callable returned must allow for null values. */
+ /** Since Maybe can have null values, the lambda returned must allow for null values. */
override fun extractTypeArg(declared: XType): XType =
declared.typeArguments.first().makeNullable()
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableInsertOrUpsertMethodBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableInsertOrUpsertMethodBinderProvider.kt
index fc23f43..9b0223d 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableInsertOrUpsertMethodBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableInsertOrUpsertMethodBinderProvider.kt
@@ -18,10 +18,11 @@
import androidx.room.compiler.processing.XRawType
import androidx.room.compiler.processing.XType
+import androidx.room.ext.KotlinTypeNames
import androidx.room.processor.Context
import androidx.room.solver.RxType
-import androidx.room.solver.shortcut.binder.CallableInsertOrUpsertMethodBinder.Companion.createInsertOrUpsertBinder
import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
+import androidx.room.solver.shortcut.binder.LambdaInsertOrUpsertMethodBinder
import androidx.room.vo.ShortcutQueryParameter
/** Provider for Rx Callable binders. */
@@ -54,9 +55,11 @@
} else {
context.typeAdapterStore.findInsertAdapter(typeArg, params)
}
- return createInsertOrUpsertBinder(typeArg, adapter) { callableImpl, _ ->
- addStatement("return %T.fromCallable(%L)", rxType.className, callableImpl)
- }
+ return LambdaInsertOrUpsertMethodBinder(
+ typeArg = typeArg,
+ functionName = rxType.factoryMethodName,
+ adapter = adapter
+ )
}
companion object {
@@ -80,25 +83,24 @@
}
/**
- * Since Completable is not a generic, the supported return type should be Void (nullable). Like
- * this, the generated Callable.call method will return Void.
+ * Since Completable has no type argument, the supported return type is Unit (non-nullable)
+ * since the 'createCompletable" factory method take a Kotlin lambda.
*/
- override fun extractTypeArg(declared: XType): XType = context.COMMON_TYPES.VOID.makeNullable()
+ override fun extractTypeArg(declared: XType): XType =
+ context.processingEnv.requireType(KotlinTypeNames.UNIT)
override fun matches(declared: XType): Boolean = isCompletable(declared)
private fun isCompletable(declared: XType): Boolean {
- if (completableType == null) {
- return false
- }
- return declared.rawType.isAssignableFrom(completableType!!)
+ val completableType = this.completableType ?: return false
+ return declared.rawType.isAssignableFrom(completableType)
}
}
private class RxSingleOrMaybeInsertOrUpsertMethodBinderProvider(context: Context, rxType: RxType) :
RxCallableInsertOrUpsertMethodBinderProvider(context, rxType) {
- /** Since Maybe can have null values, the Callable returned must allow for null values. */
+ /** Since Maybe can have null values, the lambda returned must allow for null values. */
override fun extractTypeArg(declared: XType): XType =
declared.typeArguments.first().makeNullable()
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
index 295e26f..bcfe429 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
@@ -410,7 +410,7 @@
)
} else {
val keyTypeMirror = context.processingEnv.requireType(keyTypeName)
- val set = checkNotNull(context.COMMON_TYPES.SET.typeElement)
+ val set = context.processingEnv.requireTypeElement(CommonTypeNames.SET)
val keySet = context.processingEnv.getDeclaredType(set, keyTypeMirror)
QueryParameter(
name = RelationCollectorFunctionWriter.KEY_SET_VARIABLE,
@@ -575,8 +575,9 @@
relation.field.type.let { fieldType ->
if (fieldType.typeArguments.isNotEmpty()) {
val rawType = fieldType.rawType
+ val setType = context.processingEnv.requireType(CommonTypeNames.SET)
val paramTypeName =
- if (context.COMMON_TYPES.SET.rawType.isAssignableFrom(rawType)) {
+ if (setType.rawType.isAssignableFrom(rawType)) {
when (context.codeLanguage) {
CodeLanguage.KOTLIN ->
CommonTypeNames.MUTABLE_SET.parametrizedBy(
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/ProcessorTestWrapper.kt b/room/room-compiler/src/test/kotlin/androidx/room/ProcessorTestWrapper.kt
new file mode 100644
index 0000000..ecb2da5
--- /dev/null
+++ b/room/room-compiler/src/test/kotlin/androidx/room/ProcessorTestWrapper.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room
+
+import androidx.room.compiler.processing.XProcessingEnvConfig
+import androidx.room.compiler.processing.XProcessingStep
+import androidx.room.compiler.processing.util.CompilationResultSubject
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runKspTest
+import androidx.room.compiler.processing.util.runProcessorTest
+import com.google.devtools.ksp.processing.SymbolProcessorProvider
+import java.io.File
+import javax.annotation.processing.Processor
+
+fun runProcessorTestWithK1(
+ sources: List<Source> = emptyList(),
+ classpath: List<File> = emptyList(),
+ options: Map<String, String> = emptyMap(),
+ javacArguments: List<String> = emptyList(),
+ kotlincArguments: List<String> = emptyList(),
+ handler: (XTestInvocation) -> Unit
+) {
+ androidx.room.compiler.processing.util.runProcessorTest(
+ sources = sources,
+ classpath = classpath,
+ options = options,
+ javacArguments = javacArguments,
+ kotlincArguments = listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+ handler = handler
+ )
+}
+
+fun runProcessorTestWithK1(
+ sources: List<Source> = emptyList(),
+ classpath: List<File> = emptyList(),
+ options: Map<String, String> = emptyMap(),
+ javacArguments: List<String> = emptyList(),
+ kotlincArguments: List<String> = emptyList(),
+ createProcessingSteps: () -> Iterable<XProcessingStep>,
+ onCompilationResult: (CompilationResultSubject) -> Unit
+) {
+ androidx.room.compiler.processing.util.runProcessorTest(
+ sources = sources,
+ classpath = classpath,
+ options = options,
+ javacArguments = javacArguments,
+ kotlincArguments = listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+ createProcessingSteps = createProcessingSteps,
+ onCompilationResult = onCompilationResult
+ )
+}
+
+fun runProcessorTestWithK1(
+ sources: List<Source> = emptyList(),
+ classpath: List<File> = emptyList(),
+ options: Map<String, String> = emptyMap(),
+ javacArguments: List<String> = emptyList(),
+ kotlincArguments: List<String> = emptyList(),
+ javacProcessors: List<Processor>,
+ symbolProcessorProviders: List<SymbolProcessorProvider>,
+ onCompilationResult: (CompilationResultSubject) -> Unit
+) {
+ runProcessorTest(
+ sources = sources,
+ classpath = classpath,
+ options = options,
+ javacArguments = javacArguments,
+ kotlincArguments = listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+ javacProcessors = javacProcessors,
+ symbolProcessorProviders = symbolProcessorProviders,
+ onCompilationResult = onCompilationResult
+ )
+}
+
+fun runKspTestWithK1(
+ sources: List<Source>,
+ classpath: List<File> = emptyList(),
+ options: Map<String, String> = emptyMap(),
+ javacArguments: List<String> = emptyList(),
+ kotlincArguments: List<String> = emptyList(),
+ config: XProcessingEnvConfig? = null,
+ handler: (XTestInvocation) -> Unit
+) {
+ if (config != null) {
+ runKspTest(
+ sources = sources,
+ classpath = classpath,
+ options = options,
+ javacArguments = javacArguments,
+ kotlincArguments =
+ listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+ config = config,
+ handler = handler
+ )
+ } else {
+ runKspTest(
+ sources = sources,
+ classpath = classpath,
+ options = options,
+ javacArguments = javacArguments,
+ kotlincArguments =
+ listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+ handler = handler
+ )
+ }
+}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
index f1d3a45..1e314bf 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
@@ -24,8 +24,8 @@
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.runKspTest
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runKspTestWithK1
+import androidx.room.runProcessorTestWithK1
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -232,7 +232,7 @@
"""
.trimIndent()
)
- runKspTest(
+ runKspTestWithK1(
sources = listOf(src),
config =
XProcessingEnvConfig.DEFAULT.copy(excludeMethodsWithInvalidJvmSourceNames = false)
@@ -255,7 +255,7 @@
} else {
sources to emptyList()
}
- runProcessorTest(sources = sources, classpath = classpath, handler = handler)
+ runProcessorTestWithK1(sources = sources, classpath = classpath, handler = handler)
}
private fun XTestInvocation.objectMethodNames(): List<String> {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt
index 9927769..cc19974 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt
@@ -20,7 +20,7 @@
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.XType
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
import org.junit.Test
class SQLTypeAffinityTest {
@@ -33,7 +33,7 @@
*/
@Test
fun affinityTypes() {
- runProcessorTest(sources = emptyList()) { invocation ->
+ runProcessorTestWithK1(sources = emptyList()) { invocation ->
fun XNullability.toSignature() =
if (invocation.isKsp) {
when (this) {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
index c567a4d..5c21636 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
@@ -17,7 +17,6 @@
package androidx.room.processor
import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.migration.bundle.DatabaseBundle
import androidx.room.migration.bundle.EntityBundle
import androidx.room.migration.bundle.FieldBundle
@@ -25,6 +24,7 @@
import androidx.room.migration.bundle.SchemaBundle
import androidx.room.processor.ProcessorErrors.AUTOMIGRATION_SPEC_MISSING_NOARG_CONSTRUCTOR
import androidx.room.processor.ProcessorErrors.INNER_CLASS_AUTOMIGRATION_SPEC_MUST_BE_STATIC
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import org.junit.Test
@@ -44,7 +44,7 @@
.trimIndent()
)
- runProcessorTest(listOf(source)) { invocation ->
+ runProcessorTestWithK1(listOf(source)) { invocation ->
AutoMigrationProcessor(
context = invocation.context,
spec = invocation.processingEnv.requireType("foo.bar.MyAutoMigration"),
@@ -71,7 +71,7 @@
.trimIndent()
)
- runProcessorTest(listOf(source)) { invocation ->
+ runProcessorTestWithK1(listOf(source)) { invocation ->
AutoMigrationProcessor(
context = invocation.context,
spec = invocation.processingEnv.requireType("foo.bar.MyAutoMigration"),
@@ -100,7 +100,7 @@
.trimIndent()
)
- runProcessorTest(listOf(source)) { invocation ->
+ runProcessorTestWithK1(listOf(source)) { invocation ->
AutoMigrationProcessor(
context = invocation.context,
spec =
@@ -132,7 +132,7 @@
.trimIndent()
)
- runProcessorTest(listOf(source)) { invocation ->
+ runProcessorTestWithK1(listOf(source)) { invocation ->
AutoMigrationProcessor(
context = invocation.context,
spec = invocation.processingEnv.requireType("foo.bar.MyAutoMigration"),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt
index 0d0484c..95779d9 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt
@@ -4,7 +4,7 @@
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.processing.XProcessingEnv
import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.vo.Dao
import androidx.room.writer.DaoWriter
@@ -209,7 +209,7 @@
"""
.trimIndent()
)
- runProcessorTest(sources = listOf(source)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(source)) { invocation ->
val dbElm = invocation.context.processingEnv.requireTypeElement("MyDb")
val dbType = dbElm.type
// if we could create valid code, it is good, no need for assertions.
@@ -268,8 +268,8 @@
}
"""
)
- runProcessorTest(sources = listOf(baseClass, extension, COMMON.USER, fakeDb)) { invocation
- ->
+ runProcessorTestWithK1(sources = listOf(baseClass, extension, COMMON.USER, fakeDb)) {
+ invocation ->
val daoElm = invocation.processingEnv.requireTypeElement("foo.bar.MyDao")
val dbElm = invocation.context.processingEnv.requireTypeElement("foo.bar.MyDb")
val dbType = dbElm.type
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt
index 657c1f7..d70520f 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt
@@ -19,7 +19,7 @@
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.vo.Entity
import java.io.File
@@ -61,7 +61,7 @@
} else {
baseClassReplacement = " extends $baseClass"
}
- runProcessorTest(
+ runProcessorTestWithK1(
sources =
sources +
Source.java(
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseFtsEntityParserTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseFtsEntityParserTest.kt
index 5d44695..9ac795b 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseFtsEntityParserTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseFtsEntityParserTest.kt
@@ -18,7 +18,7 @@
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.vo.FtsEntity
import java.io.File
@@ -76,7 +76,8 @@
baseClassReplacement
) + input + ENTITY_SUFFIX
)
- runProcessorTest(sources = sources + entitySource, classpath = classpath) { invocation ->
+ runProcessorTestWithK1(sources = sources + entitySource, classpath = classpath) { invocation
+ ->
val entity = invocation.processingEnv.requireTypeElement("foo.bar.MyEntity")
val processor = FtsTableEntityProcessor(invocation.context, entity)
val processedEntity = processor.process()
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
index ea33b1c..e48920a 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
@@ -28,7 +28,6 @@
import androidx.room.compiler.codegen.XTypeSpec.Builder.Companion.apply
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.ext.CommonTypeNames
import androidx.room.ext.CommonTypeNames.MUTABLE_LIST
import androidx.room.ext.CommonTypeNames.STRING
@@ -37,6 +36,7 @@
import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR
import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_BE_PUBLIC
import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_UNBOUND_GENERIC
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.vo.CustomTypeConverter
import com.squareup.javapoet.TypeVariableName
@@ -256,7 +256,7 @@
.build()
.toString()
)
- runProcessorTest(sources = listOf(baseConverter, extendingClass)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(baseConverter, extendingClass)) { invocation ->
val element =
invocation.processingEnv.requireTypeElement(extendingClassName.canonicalName)
val converter =
@@ -339,7 +339,7 @@
public class Container {}
"""
)
- runProcessorTest(listOf(source)) { invocation ->
+ runProcessorTestWithK1(listOf(source)) { invocation ->
val result =
CustomConverterProcessor.findConverters(
invocation.context,
@@ -405,7 +405,7 @@
vararg sources: Source,
handler: (CustomTypeConverter?, XTestInvocation) -> Unit
) {
- runProcessorTest(sources = sources.toList() + CONTAINER) { invocation ->
+ runProcessorTestWithK1(sources = sources.toList() + CONTAINER) { invocation ->
val processed =
CustomConverterProcessor.findConverters(
invocation.context,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
index 4306440..8956655 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
@@ -22,11 +22,11 @@
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.runKspTest
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.ext.RoomTypeNames.ROOM_DB
import androidx.room.processor.ProcessorErrors.nullableCollectionOrArrayReturnTypeInDaoMethod
import androidx.room.processor.ProcessorErrors.nullableComponentInDaoMethodReturnType
+import androidx.room.runKspTestWithK1
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.vo.Dao
import androidx.room.vo.ReadQueryMethod
@@ -263,7 +263,7 @@
"""
.trimIndent()
)
- runProcessorTest(sources = listOf(daoSrc) + COMMON.USER) { invocation ->
+ runProcessorTestWithK1(sources = listOf(daoSrc) + COMMON.USER) { invocation ->
val dao =
invocation.roundEnv
.getElementsAnnotatedWith(androidx.room.Dao::class.qualifiedName!!)
@@ -452,7 +452,7 @@
"""
.trimIndent()
)
- runProcessorTest(sources = listOf(source)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(source)) { invocation ->
val dao = invocation.processingEnv.requireTypeElement("MyDao")
val dbType = invocation.context.processingEnv.requireType(ROOM_DB)
DaoProcessor(
@@ -490,7 +490,7 @@
"""
.trimIndent()
)
- runKspTest(
+ runKspTestWithK1(
sources = listOf(src),
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
) { invocation ->
@@ -528,7 +528,7 @@
"""
.trimIndent()
)
- runKspTest(
+ runKspTestWithK1(
sources = listOf(src),
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
) { invocation ->
@@ -569,7 +569,7 @@
"""
.trimIndent()
)
- runKspTest(
+ runKspTestWithK1(
sources = listOf(src),
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
) { invocation ->
@@ -641,7 +641,7 @@
"""
.trimIndent()
)
- runKspTest(
+ runKspTestWithK1(
sources = listOf(src),
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
) { invocation ->
@@ -767,7 +767,7 @@
"""
.trimIndent()
)
- runKspTest(
+ runKspTestWithK1(
sources = listOf(src),
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
) { invocation ->
@@ -838,7 +838,7 @@
classpathFiles: List<File> = emptyList(),
handler: (Dao, XTestInvocation) -> Unit
) {
- runProcessorTest(
+ runProcessorTestWithK1(
sources =
listOf(
Source.java("foo.bar.MyDao", DAO_PREFIX + inputs.joinToString("\n")),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseConstructorProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseConstructorProcessorTest.kt
index 44ec92f..d66a941 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseConstructorProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseConstructorProcessorTest.kt
@@ -19,7 +19,7 @@
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runKspTest
+import androidx.room.runKspTestWithK1
import androidx.room.testing.context
import org.junit.Test
@@ -136,7 +136,7 @@
}
private fun runTest(constructorSource: Source, handler: (XTestInvocation) -> Unit = { _ -> }) {
- runKspTest(sources = listOf(databaseSource, constructorSource)) { invocation ->
+ runKspTestWithK1(sources = listOf(databaseSource, constructorSource)) { invocation ->
val entity =
invocation.roundEnv
.getElementsAnnotatedWith(androidx.room.Database::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
index 5885790..885b353 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
@@ -29,12 +29,12 @@
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compileFiles
import androidx.room.compiler.processing.util.compileFilesIntoJar
-import androidx.room.compiler.processing.util.runKspTest
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.parser.ParsedQuery
import androidx.room.parser.QueryType
import androidx.room.parser.Table
import androidx.room.processor.ProcessorErrors.invalidAutoMigrationSchema
+import androidx.room.runKspTestWithK1
+import androidx.room.runProcessorTestWithK1
import androidx.room.solver.query.result.EntityRowAdapter
import androidx.room.solver.query.result.PojoRowAdapter
import androidx.room.testing.context
@@ -508,7 +508,7 @@
}
"""
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = listOf(BOOK, BOOK_DAO, DB1, DB2, db1_2),
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
createProcessingSteps = { listOf(DatabaseProcessingStep()) }
@@ -1257,7 +1257,7 @@
}
"""
)
- runProcessorTest(sources = listOf(badDaoType)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(badDaoType)) { invocation ->
val element = invocation.processingEnv.requireTypeElement("foo.bar.MyDb")
val result =
DatabaseProcessor(baseContext = invocation.context, element = element).process()
@@ -1281,7 +1281,7 @@
}
"""
)
- runProcessorTest(listOf(badDaoType)) { invocation ->
+ runProcessorTestWithK1(listOf(badDaoType)) { invocation ->
val element = invocation.processingEnv.requireTypeElement("foo.bar.MyDb")
val result =
DatabaseProcessor(baseContext = invocation.context, element = element).process()
@@ -1503,7 +1503,7 @@
"""
.trimIndent()
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = listOf(dbSource, USER),
options = mapOf("room.schemaLocation" to "schemas/", "room.generateKotlin" to "false")
) { invocation ->
@@ -1561,7 +1561,7 @@
.trimIndent()
)
}
- runProcessorTest(
+ runProcessorTestWithK1(
sources = listOf(dbSource, USER),
javacProcessors = listOf(RoomProcessor()),
symbolProcessorProviders = listOf(RoomKspProcessor.Provider()),
@@ -1591,7 +1591,7 @@
}
"""
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = listOf(jvmNameInDaoGetter),
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
) { invocation ->
@@ -1630,7 +1630,7 @@
"""
.trimIndent()
)
- runKspTest(
+ runKspTestWithK1(
sources = listOf(src),
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
) { invocation ->
@@ -1663,7 +1663,7 @@
views: Map<String, Set<String>>,
body: (List<DatabaseView>, XTestInvocation) -> Unit
) {
- runProcessorTest(
+ runProcessorTestWithK1(
sources = listOf(DB3, BOOK),
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
) { invocation ->
@@ -1716,7 +1716,7 @@
}
"""
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = listOf(BOOK, bookDao) + dbs,
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
createProcessingSteps = { listOf(DatabaseProcessingStep()) },
@@ -1731,7 +1731,7 @@
classpath: List<File> = emptyList(),
handler: (Database, XTestInvocation) -> Unit
) {
- runProcessorTest(
+ runProcessorTestWithK1(
sources = otherFiles.toList() + Source.java("foo.bar.MyDb", DATABASE_PREFIX + input),
classpath = classpath,
options =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseViewProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseViewProcessorTest.kt
index d9d5117..a00de28 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseViewProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseViewProcessorTest.kt
@@ -19,9 +19,9 @@
import androidx.kruth.assertThat
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.parser.ParserErrors
import androidx.room.parser.SQLTypeAffinity
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.verifier.ColumnInfo
import androidx.room.vo.DatabaseView
@@ -235,7 +235,7 @@
verify: Boolean = true,
handler: (view: DatabaseView, invocation: XTestInvocation) -> Unit
) {
- runProcessorTest(sources = sources + Source.java(name, DATABASE_PREFIX + input)) {
+ runProcessorTestWithK1(sources = sources + Source.java(name, DATABASE_PREFIX + input)) {
invocation ->
val view = invocation.processingEnv.requireTypeElement(name)
val verifier =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
index 4c5d5a1..18f90c1 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
@@ -28,7 +28,6 @@
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.ext.CommonTypeNames
import androidx.room.ext.GuavaUtilConcurrentTypeNames
import androidx.room.ext.KotlinTypeNames
@@ -36,6 +35,7 @@
import androidx.room.ext.ReactiveStreamsTypeNames
import androidx.room.ext.RxJava2TypeNames
import androidx.room.ext.RxJava3TypeNames
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.vo.DeleteOrUpdateShortcutMethod
import kotlin.reflect.KClass
@@ -770,7 +770,7 @@
COMMON.LISTENABLE_FUTURE,
COMMON.GUAVA_ROOM
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = commonSources + additionalSources + inputSource,
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
) { invocation ->
@@ -826,7 +826,7 @@
COMMON.GUAVA_ROOM
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = commonSources + additionalSources + inputSource,
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
index 0cfc88a4..63256c3 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
@@ -24,8 +24,10 @@
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.ext.CommonTypeNames
import androidx.room.parser.Collate
import androidx.room.parser.SQLTypeAffinity
+import androidx.room.runProcessorTestWithK1
import androidx.room.solver.types.ColumnTypeAdapter
import androidx.room.testing.context
import androidx.room.vo.Field
@@ -600,7 +602,10 @@
`is`(
Field(
name = "code",
- type = invocation.context.COMMON_TYPES.STRING.makeNullable(),
+ type =
+ invocation.context.processingEnv
+ .requireType(CommonTypeNames.STRING)
+ .makeNullable(),
element = field.element,
columnName = "code",
collate = collate,
@@ -702,7 +707,7 @@
),
ARRAY_CONVERTER
)
- runProcessorTest(sources = sources) { invocation ->
+ runProcessorTestWithK1(sources = sources) { invocation ->
val (owner, fieldElement) =
invocation.roundEnv
.getElementsAnnotatedWith(Entity::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt
index b8ed3770..9d32327 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt
@@ -20,9 +20,9 @@
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.parser.FtsVersion
import androidx.room.parser.SQLTypeAffinity
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.vo.CallType
import androidx.room.vo.Field
@@ -86,7 +86,7 @@
@Test
fun missingEntityAnnotation() {
- runProcessorTest(
+ runProcessorTestWithK1(
sources =
listOf(
Source.java(
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/GeneratedCustomConverterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/GeneratedCustomConverterTest.kt
index 6051837..d63633f 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/GeneratedCustomConverterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/GeneratedCustomConverterTest.kt
@@ -26,7 +26,7 @@
import androidx.room.compiler.processing.javac.JavacBasicAnnotationProcessor
import androidx.room.compiler.processing.ksp.KspBasicAnnotationProcessor
import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.squareup.javapoet.ClassName
@@ -75,7 +75,7 @@
"""
.trimIndent()
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = listOf(src),
javacProcessors = listOf(RoomProcessor(), JavacCustomConverter()),
symbolProcessorProviders =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
index 857704e..f0ddca5 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
@@ -28,7 +28,6 @@
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.ext.CommonTypeNames
import androidx.room.ext.GuavaUtilConcurrentTypeNames
import androidx.room.ext.KotlinTypeNames
@@ -36,6 +35,7 @@
import androidx.room.ext.ReactiveStreamsTypeNames
import androidx.room.ext.RxJava2TypeNames
import androidx.room.ext.RxJava3TypeNames
+import androidx.room.runProcessorTestWithK1
import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
import androidx.room.testing.context
import androidx.room.vo.InsertOrUpsertShortcutMethod
@@ -1139,7 +1139,7 @@
COMMON.RX3_SINGLE
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = commonSources + additionalSources + inputSource,
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
) { invocation ->
@@ -1194,7 +1194,7 @@
COMMON.GUAVA_ROOM
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = commonSources + additionalSources + inputSource,
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTargetMethodTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTargetMethodTest.kt
index 75abd32..8de7576 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTargetMethodTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTargetMethodTest.kt
@@ -19,7 +19,7 @@
import androidx.room.compiler.codegen.XClassName
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import org.junit.Test
import org.junit.runner.RunWith
@@ -461,7 +461,7 @@
}
private fun singleRun(vararg sources: Source, handler: ((XTestInvocation) -> Unit)? = null) {
- runProcessorTest(sources = sources.toList()) { invocation ->
+ runProcessorTestWithK1(sources = sources.toList()) { invocation ->
PojoProcessor.createFor(
context = invocation.context,
element = invocation.processingEnv.requireTypeElement(MY_POJO),
@@ -506,7 +506,7 @@
val pojoSource = Source.java(MY_POJO.canonicalName, pojoCode)
val autoValuePojoSource = Source.java(AUTOVALUE_MY_POJO.canonicalName, autoValuePojoCode)
val all = sources.toList() + pojoSource + autoValuePojoSource
- return runProcessorTest(sources = all) { invocation ->
+ return runProcessorTestWithK1(sources = all) { invocation ->
PojoProcessor.createFor(
context = invocation.context,
element = invocation.processingEnv.requireTypeElement(MY_POJO),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
index 9a2fa5d..3bebe36 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
@@ -23,7 +23,7 @@
import androidx.room.compiler.processing.XFieldElement
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.ext.CommonTypeNames
import androidx.room.parser.SQLTypeAffinity
import androidx.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD
import androidx.room.processor.ProcessorErrors.MISSING_POJO_CONSTRUCTOR
@@ -33,6 +33,7 @@
import androidx.room.processor.ProcessorErrors.relationCannotFindJunctionEntityField
import androidx.room.processor.ProcessorErrors.relationCannotFindJunctionParentField
import androidx.room.processor.ProcessorErrors.relationCannotFindParentEntityField
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.vo.CallType
import androidx.room.vo.Constructor
@@ -84,7 +85,7 @@
public void setBaseField(String baseField){ }
}
"""
- runProcessorTest(
+ runProcessorTestWithK1(
sources =
listOf(
Source.java(
@@ -1054,7 +1055,7 @@
$FOOTER
"""
)
- runProcessorTest(sources = listOf(pojo)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(pojo)) { invocation ->
val element = invocation.processingEnv.requireTypeElement(MY_POJO)
val pojo1 =
PojoProcessor.createFor(
@@ -1108,7 +1109,7 @@
.process()
assertThat(pojo5, sameInstance(pojo4))
- val type = invocation.context.COMMON_TYPES.STRING
+ val type = invocation.context.processingEnv.requireType(CommonTypeNames.STRING)
val mockElement = mock(XFieldElement::class.java)
doReturn(type).`when`(mockElement).type
val fakeField =
@@ -1706,7 +1707,7 @@
"foo.bar.TestData.WithJvmOverloads"
)
.forEach {
- runProcessorTest(sources = listOf(TEST_DATA)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(TEST_DATA)) { invocation ->
PojoProcessor.createFor(
context = invocation.context,
element = invocation.processingEnv.requireTypeElement(it),
@@ -1721,7 +1722,7 @@
@Test
fun dataClass_withJvmOverloads_primaryConstructor() {
- runProcessorTest(sources = listOf(TEST_DATA)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(TEST_DATA)) { invocation ->
PojoProcessor.createFor(
context = invocation.context,
element =
@@ -1751,7 +1752,7 @@
}
"""
)
- runProcessorTest(sources = listOf(source)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(source)) { invocation ->
val pojo =
PojoProcessor.createFor(
context = invocation.context,
@@ -1767,7 +1768,7 @@
@Test
fun ignoredColumns_noConstructor() {
- runProcessorTest(
+ runProcessorTestWithK1(
listOf(
Source.java(
MY_POJO.canonicalName,
@@ -1806,7 +1807,7 @@
@Test
fun ignoredColumns_noSetterGetter() {
- runProcessorTest(
+ runProcessorTestWithK1(
listOf(
Source.java(
MY_POJO.canonicalName,
@@ -1843,7 +1844,7 @@
@Test
fun ignoredColumns_columnInfo() {
- runProcessorTest(
+ runProcessorTestWithK1(
listOf(
Source.java(
MY_POJO.canonicalName,
@@ -1875,7 +1876,7 @@
@Test
fun ignoredColumns_missing() {
- runProcessorTest(
+ runProcessorTestWithK1(
listOf(
Source.java(
MY_POJO.canonicalName,
@@ -1909,7 +1910,7 @@
@Test
fun noSetter_scopeBindStmt() {
- runProcessorTest(
+ runProcessorTestWithK1(
listOf(
Source.java(
MY_POJO.canonicalName,
@@ -1938,7 +1939,7 @@
@Test
fun noSetter_scopeTwoWay() {
- runProcessorTest(
+ runProcessorTestWithK1(
listOf(
Source.java(
MY_POJO.canonicalName,
@@ -1970,7 +1971,7 @@
@Test
fun noSetter_scopeReadFromCursor() {
- runProcessorTest(
+ runProcessorTestWithK1(
listOf(
Source.java(
MY_POJO.canonicalName,
@@ -2002,7 +2003,7 @@
@Test
fun noGetter_scopeBindStmt() {
- runProcessorTest(
+ runProcessorTestWithK1(
listOf(
Source.java(
MY_POJO.canonicalName,
@@ -2034,7 +2035,7 @@
@Test
fun noGetter_scopeTwoWay() {
- runProcessorTest(
+ runProcessorTestWithK1(
listOf(
Source.java(
MY_POJO.canonicalName,
@@ -2066,7 +2067,7 @@
@Test
fun noGetter_scopeReadCursor() {
- runProcessorTest(
+ runProcessorTestWithK1(
listOf(
Source.java(
MY_POJO.canonicalName,
@@ -2095,7 +2096,7 @@
@Test
fun setterStartsWithIs() {
- runProcessorTest(
+ runProcessorTestWithK1(
listOf(
Source.kotlin(
"Book.kt",
@@ -2119,7 +2120,7 @@
)
.process()
val fields = result.fields.associateBy { it.name }
- val stringType = invocation.context.COMMON_TYPES.STRING
+ val stringType = invocation.context.processingEnv.requireType(CommonTypeNames.STRING)
assertThat(fields["isbn"]?.getter)
.isEqualTo(
FieldGetter(
@@ -2163,7 +2164,7 @@
@Test
fun embedded_nullability() {
listOf("foo.bar.TestData.SomeEmbeddedVals").forEach {
- runProcessorTest(sources = listOf(TEST_DATA)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(TEST_DATA)) { invocation ->
val result =
PojoProcessor.createFor(
context = invocation.context,
@@ -2211,7 +2212,7 @@
) {
val pojoSource = Source.java(MY_POJO.canonicalName, code)
val all = sources.toList() + pojoSource
- runProcessorTest(sources = all, classpath = classpath) { invocation ->
+ runProcessorTestWithK1(sources = all, classpath = classpath) { invocation ->
handler.invoke(
PojoProcessor.createFor(
context = invocation.context,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/ProjectionExpanderTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/ProjectionExpanderTest.kt
index f4bf2e8..124fccd 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/ProjectionExpanderTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/ProjectionExpanderTest.kt
@@ -20,9 +20,9 @@
import androidx.room.compiler.processing.isTypeElement
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.parser.SqlParser
import androidx.room.parser.expansion.ProjectionExpander
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import createVerifierFromEntitiesAndViews
import org.hamcrest.CoreMatchers.equalTo
@@ -521,7 +521,7 @@
@Test
fun joinAndAbandonEntity() {
- runProcessorTest(sources = ENTITIES) { invocation ->
+ runProcessorTestWithK1(sources = ENTITIES) { invocation ->
val entities =
invocation.roundEnv
.getElementsAnnotatedWith(androidx.room.Entity::class.qualifiedName!!)
@@ -613,7 +613,7 @@
val extraSource =
input?.let { listOf(Source.java(name, DATABASE_PREFIX + input)) } ?: emptyList()
val all = ENTITIES + extraSource
- return runProcessorTest(sources = all) { invocation ->
+ return runProcessorTestWithK1(sources = all) { invocation ->
val entities =
invocation.roundEnv
.getElementsAnnotatedWith(androidx.room.Entity::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
index 1c92cc9..6ab007f 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
@@ -27,7 +27,6 @@
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.ext.CommonTypeNames
import androidx.room.ext.CommonTypeNames.LIST
import androidx.room.ext.CommonTypeNames.MUTABLE_LIST
@@ -46,6 +45,7 @@
import androidx.room.processor.ProcessorErrors.MAP_INFO_MUST_HAVE_AT_LEAST_ONE_COLUMN_PROVIDED
import androidx.room.processor.ProcessorErrors.cannotFindQueryResultAdapter
import androidx.room.processor.ProcessorErrors.mayNeedMapColumn
+import androidx.room.runProcessorTestWithK1
import androidx.room.solver.query.result.DataSourceFactoryQueryResultBinder
import androidx.room.solver.query.result.ListQueryResultAdapter
import androidx.room.solver.query.result.LiveDataQueryResultBinder
@@ -1239,7 +1239,7 @@
)
val allOptions =
mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false") + options
- runProcessorTest(
+ runProcessorTestWithK1(
sources = additionalSources + commonSources + inputSource,
options = allOptions
) { invocation ->
@@ -1309,7 +1309,7 @@
COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = additionalSources + commonSources + inputSource,
options = options
) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
index 173a78a..d9cc959 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
@@ -35,6 +35,7 @@
import androidx.room.ext.RxJava3TypeNames
import androidx.room.ext.SupportDbTypeNames
import androidx.room.processor.ProcessorErrors.RAW_QUERY_STRING_PARAMETER_REMOVED
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.vo.RawQueryMethod
import androidx.sqlite.db.SupportSQLiteQuery
@@ -644,7 +645,7 @@
COMMON.IMAGE_FORMAT,
COMMON.CONVERTER
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = commonSources + inputSource,
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
) { invocation ->
@@ -698,7 +699,7 @@
COMMON.FLOW,
COMMON.GUAVA_ROOM
)
- runProcessorTest(sources = commonSources + inputSource) { invocation ->
+ runProcessorTestWithK1(sources = commonSources + inputSource) { invocation ->
val (owner, methods) =
invocation.roundEnv
.getElementsAnnotatedWith(Dao::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/RemoveUnusedColumnsTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/RemoveUnusedColumnsTest.kt
index 4ffa12b..447dca2 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/RemoveUnusedColumnsTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/RemoveUnusedColumnsTest.kt
@@ -21,7 +21,7 @@
import androidx.room.RewriteQueriesToDropUnusedColumns
import androidx.room.compiler.processing.util.CompilationResultSubject
import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -90,7 +90,7 @@
annotateMethod = annotateMethod
) + COMMON.USER
- runProcessorTest(
+ runProcessorTestWithK1(
sources = sources,
createProcessingSteps = { listOf(DatabaseProcessingStep()) },
options =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
index 109db0a..7d3459a 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
@@ -23,9 +23,9 @@
import androidx.room.compiler.codegen.XTypeName.Companion.PRIMITIVE_LONG
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.parser.SQLTypeAffinity
import androidx.room.processor.ProcessorErrors.RELATION_IN_ENTITY
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.vo.CallType
import androidx.room.vo.Field
@@ -2093,16 +2093,9 @@
sources = listOf(COMMON.USER)
) { _, invocation ->
invocation.assertCompilationResult {
- // TODO: https://github.com/google/ksp/issues/603
- // KSP validator does not validate annotation types so we will get another error
- // down the line.
- if (invocation.isKsp) {
- hasErrorContaining(ProcessorErrors.foreignKeyNotAnEntity("<Error>")).onLine(11)
- } else {
- hasErrorContaining(
- "Element 'foo.bar.MyEntity' references a type that is not present"
- )
- }
+ hasErrorContaining(
+ "Element 'foo.bar.MyEntity' references a type that is not present"
+ )
}
}
}
@@ -2637,7 +2630,7 @@
"""
.trimIndent()
)
- runProcessorTest(sources = listOf(src)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(src)) { invocation ->
val parser =
TableEntityProcessor(
invocation.context,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt
index cc9d309..9354625 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt
@@ -23,7 +23,6 @@
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.ext.GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE
import androidx.room.ext.KotlinTypeNames.FLOW
import androidx.room.ext.LifecyclesTypeNames.COMPUTABLE_LIVE_DATA
@@ -31,6 +30,7 @@
import androidx.room.ext.ReactiveStreamsTypeNames.PUBLISHER
import androidx.room.ext.RxJava2TypeNames
import androidx.room.ext.RxJava3TypeNames
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.vo.TransactionMethod
import org.hamcrest.CoreMatchers.`is`
@@ -342,7 +342,7 @@
COMMON.LISTENABLE_FUTURE,
COMMON.FLOW
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = inputSource + otherSources,
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false")
) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt
index 9c942db..c36fc4b 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt
@@ -20,10 +20,10 @@
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.processor.FieldProcessor
import androidx.room.processor.PojoProcessor
import androidx.room.processor.ProcessorErrors
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.vo.Pojo
import com.google.auto.value.processor.AutoValueProcessor
@@ -112,7 +112,7 @@
// between javac (argN) and kotlinc (pN).
javacArguments = listOf("-parameters")
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = emptyList(),
classpath = libraryClasspath,
) { invocation: XTestInvocation ->
@@ -281,7 +281,7 @@
val pojoSource = Source.java(MY_POJO.canonicalName, pojoCode)
val autoValuePojoSource = Source.java(AUTOVALUE_MY_POJO.canonicalName, autoValuePojoCode)
val all: List<Source> = sources.toList() + pojoSource + autoValuePojoSource
- runProcessorTest(sources = all, classpath = classpathFiles) { invocation ->
+ runProcessorTestWithK1(sources = all, classpath = classpathFiles) { invocation ->
handler.invoke(
PojoProcessor.createFor(
context = invocation.context,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/BuiltInConverterFlagsTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/BuiltInConverterFlagsTest.kt
index a53c6e7..6ced559 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/BuiltInConverterFlagsTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/BuiltInConverterFlagsTest.kt
@@ -23,10 +23,10 @@
import androidx.room.DatabaseProcessingStep
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.processor.Context
import androidx.room.processor.ProcessorErrors.CANNOT_FIND_COLUMN_TYPE_ADAPTER
import androidx.room.processor.ProcessorErrors.CANNOT_FIND_CURSOR_READER
+import androidx.room.runProcessorTestWithK1
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -138,7 +138,7 @@
daoAnnotation = daoAnnotation,
dbAnnotation = dbAnnotation
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = listOf(source),
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt
index 3528c71..3bfb880 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt
@@ -31,11 +31,11 @@
import androidx.room.compiler.codegen.XTypeSpec
import androidx.room.compiler.processing.util.CompilationResultSubject
import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.ext.CommonTypeNames
import androidx.room.ext.RoomAnnotationTypeNames
import androidx.room.ext.RoomTypeNames.ROOM_DB
import androidx.room.processor.ProcessorErrors.CANNOT_BIND_QUERY_PARAMETER_INTO_STMT
+import androidx.room.runProcessorTestWithK1
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -212,7 +212,7 @@
sources: List<Source>,
onCompilationResult: (CompilationResultSubject) -> Unit = { it.hasErrorCount(0) }
) {
- runProcessorTest(
+ runProcessorTestWithK1(
sources =
sources +
CUSTOM_TYPE_JFO +
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt
index 9842dd3..9ed50f1 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt
@@ -25,11 +25,12 @@
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compiler.TestCompilationArguments
import androidx.room.compiler.processing.util.compiler.compile
-import androidx.room.compiler.processing.util.runKspTest
import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.processor.Context.BooleanProcessorOptions.USE_NULL_AWARE_CONVERTER
import androidx.room.processor.CustomConverterProcessor
import androidx.room.processor.DaoProcessor
+import androidx.room.runKspTestWithK1
+import androidx.room.runProcessorTestWithK1
import androidx.room.solver.types.CustomTypeConverterWrapper
import androidx.room.solver.types.TypeConverter
import androidx.room.testing.context
@@ -405,7 +406,7 @@
"""
.trimIndent()
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = listOf(user, day, converters, dao),
options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
) { invocation ->
@@ -604,7 +605,7 @@
"""
.trimIndent()
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = listOf(source),
options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
) { invocation ->
@@ -680,7 +681,7 @@
"""
.trimIndent()
)
- runKspTest(
+ runKspTestWithK1(
sources = listOf(converters),
options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
) { invocation ->
@@ -738,7 +739,7 @@
"""
.trimIndent()
)
- runKspTest(sources = listOf(source)) { invocation ->
+ runKspTestWithK1(sources = listOf(source)) { invocation ->
val store = invocation.createStore("TimeConverter", "AwesomenessConverter")
val instantType = invocation.processingEnv.requireType("java.time.Instant")
val stringType = invocation.processingEnv.requireType("java.lang.String")
@@ -752,7 +753,7 @@
/** Collect results for conversion from String to our type */
private fun collectStringConversionResults(vararg selectedConverters: String): String {
val result = StringBuilder()
- runProcessorTest(
+ runProcessorTestWithK1(
sources = listOf(source),
options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
) { invocation ->
@@ -800,7 +801,7 @@
/** Collect results for conversion from an unknown cursor type to our type */
private fun collectCursorResults(vararg selectedConverters: String): String {
val result = StringBuilder()
- runProcessorTest(
+ runProcessorTestWithK1(
sources = listOf(source),
options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
index 0ebee23..29cbd79 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
@@ -29,6 +29,7 @@
import androidx.room.compiler.processing.isTypeElement
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.compileFiles
import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.ext.CommonTypeNames
import androidx.room.ext.GuavaUtilConcurrentTypeNames
@@ -46,6 +47,7 @@
import androidx.room.processor.DaoProcessor
import androidx.room.processor.DaoProcessorTest
import androidx.room.processor.ProcessorErrors
+import androidx.room.runProcessorTestWithK1
import androidx.room.solver.binderprovider.DataSourceFactoryQueryResultBinderProvider
import androidx.room.solver.binderprovider.DataSourceQueryResultBinderProvider
import androidx.room.solver.binderprovider.ListenableFuturePagingSourceQueryResultBinderProvider
@@ -137,7 +139,7 @@
"""
.trimIndent()
)
- runProcessorTest(sources = listOf(entity, converter)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(entity, converter)) { invocation ->
val typeElement =
invocation.processingEnv.requireTypeElement("foo.bar.EntityWithOneWayEnum")
val context = Context(invocation.processingEnv)
@@ -201,7 +203,7 @@
"""
.trimMargin()
)
- runProcessorTest(sources = listOf(enumSrc)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(enumSrc)) { invocation ->
val store =
TypeAdapterStore.create(
Context(invocation.processingEnv),
@@ -234,7 +236,7 @@
)
var results: Map<String, String?> = mutableMapOf()
- runProcessorTest(sources = listOf(source)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(source)) { invocation ->
val typeAdapterStore =
TypeAdapterStore.create(
context = invocation.context,
@@ -290,7 +292,7 @@
.trimIndent()
)
- runProcessorTest(sources = listOf(source)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(source)) { invocation ->
TypeAdapterStore.create(
context = invocation.context,
builtInConverterFlags = BuiltInConverterFlags.DEFAULT
@@ -407,7 +409,7 @@
}
"""
)
- runProcessorTest(sources = listOf(point)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(point)) { invocation ->
val context = Context(invocation.processingEnv)
val converters =
CustomConverterProcessor(
@@ -532,7 +534,7 @@
val converter =
store.typeConverterStore.findTypeConverter(
binders[0].from,
- invocation.context.COMMON_TYPES.STRING
+ invocation.context.processingEnv.requireType(CommonTypeNames.STRING)
)
assertThat(converter).isNotNull()
assertThat(store.typeConverterStore.reverse(converter!!)).isEqualTo(binders[1])
@@ -559,7 +561,7 @@
val converter =
store.typeConverterStore.findTypeConverter(
binders[0].from,
- invocation.context.COMMON_TYPES.STRING
+ invocation.context.processingEnv.requireType(CommonTypeNames.STRING)
)
assertThat(converter, notNullValue())
assertThat(store.typeConverterStore.reverse(converter!!), nullValue())
@@ -568,7 +570,8 @@
@Test
fun testMissingRx2Room() {
- runProcessorTest(sources = listOf(COMMON.PUBLISHER, COMMON.RX2_FLOWABLE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.PUBLISHER, COMMON.RX2_FLOWABLE)) { invocation
+ ->
val publisherElement =
invocation.processingEnv.requireTypeElement(ReactiveStreamsTypeNames.PUBLISHER)
assertThat(publisherElement, notNullValue())
@@ -586,7 +589,8 @@
@Test
fun testMissingRx3Room() {
- runProcessorTest(sources = listOf(COMMON.PUBLISHER, COMMON.RX3_FLOWABLE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.PUBLISHER, COMMON.RX3_FLOWABLE)) { invocation
+ ->
val publisherElement =
invocation.processingEnv.requireTypeElement(ReactiveStreamsTypeNames.PUBLISHER)
assertThat(publisherElement, notNullValue())
@@ -625,7 +629,8 @@
@Test
fun testMissingRoomPagingGuava() {
- runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) {
+ invocation ->
val listenableFuturePagingSourceElement =
invocation.processingEnv.requireTypeElement(
PagingTypeNames.LISTENABLE_FUTURE_PAGING_SOURCE
@@ -652,7 +657,7 @@
@Test
fun testMissingRoomPagingRx2() {
- runProcessorTest(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
val rx2PagingSourceElement =
invocation.processingEnv.requireTypeElement(PagingTypeNames.RX2_PAGING_SOURCE)
val intType = invocation.processingEnv.requireType(Integer::class)
@@ -673,7 +678,7 @@
@Test
fun testMissingRoomPagingRx3() {
- runProcessorTest(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
val rx3PagingSourceElement =
invocation.processingEnv.requireTypeElement(PagingTypeNames.RX3_PAGING_SOURCE)
val intType = invocation.processingEnv.requireType(Integer::class)
@@ -696,16 +701,21 @@
fun testFindPublisher() {
listOf(COMMON.RX2_FLOWABLE to COMMON.RX2_ROOM, COMMON.RX3_FLOWABLE to COMMON.RX3_ROOM)
.forEach { (rxTypeSrc, rxRoomSrc) ->
- runProcessorTest(
- sources =
- listOf(
- COMMON.RX2_SINGLE,
- COMMON.RX3_SINGLE,
- COMMON.RX2_OBSERVABLE,
- COMMON.RX3_OBSERVABLE,
- COMMON.PUBLISHER,
- rxTypeSrc,
- rxRoomSrc
+ runProcessorTestWithK1(
+ sources = listOf(rxTypeSrc, rxRoomSrc),
+ classpath =
+ compileFiles(
+ listOf(
+ COMMON.RX2_SINGLE,
+ COMMON.RX2_MAYBE,
+ COMMON.RX2_COMPLETABLE,
+ COMMON.RX2_OBSERVABLE,
+ COMMON.RX3_SINGLE,
+ COMMON.RX3_MAYBE,
+ COMMON.RX3_COMPLETABLE,
+ COMMON.RX3_OBSERVABLE,
+ COMMON.PUBLISHER,
+ )
)
) { invocation ->
val publisher =
@@ -730,16 +740,21 @@
Triple(COMMON.RX3_FLOWABLE, COMMON.RX3_ROOM, RxJava3TypeNames.FLOWABLE)
)
.forEach { (rxTypeSrc, rxRoomSrc, rxTypeClassName) ->
- runProcessorTest(
- sources =
- listOf(
- COMMON.RX2_SINGLE,
- COMMON.RX3_SINGLE,
- COMMON.RX2_OBSERVABLE,
- COMMON.RX3_OBSERVABLE,
- COMMON.PUBLISHER,
- rxTypeSrc,
- rxRoomSrc
+ runProcessorTestWithK1(
+ sources = listOf(rxTypeSrc, rxRoomSrc),
+ classpath =
+ compileFiles(
+ listOf(
+ COMMON.RX2_SINGLE,
+ COMMON.RX2_MAYBE,
+ COMMON.RX2_COMPLETABLE,
+ COMMON.RX2_OBSERVABLE,
+ COMMON.RX3_SINGLE,
+ COMMON.RX3_MAYBE,
+ COMMON.RX3_COMPLETABLE,
+ COMMON.RX3_OBSERVABLE,
+ COMMON.PUBLISHER,
+ )
)
) { invocation ->
val flowable = invocation.processingEnv.requireTypeElement(rxTypeClassName)
@@ -760,16 +775,23 @@
Triple(COMMON.RX3_OBSERVABLE, COMMON.RX3_ROOM, RxJava3TypeNames.OBSERVABLE)
)
.forEach { (rxTypeSrc, rxRoomSrc, rxTypeClassName) ->
- runProcessorTest(
- sources =
- listOf(
- COMMON.RX2_SINGLE,
- COMMON.RX3_SINGLE,
- COMMON.RX2_FLOWABLE,
- COMMON.RX3_FLOWABLE,
- COMMON.PUBLISHER,
- rxTypeSrc,
- rxRoomSrc
+ runProcessorTestWithK1(
+ sources = listOf(rxTypeSrc, rxRoomSrc),
+ classpath =
+ compileFiles(
+ listOf(
+ COMMON.RX2_SINGLE,
+ COMMON.RX2_MAYBE,
+ COMMON.RX2_COMPLETABLE,
+ COMMON.RX2_OBSERVABLE,
+ COMMON.RX2_FLOWABLE,
+ COMMON.RX3_SINGLE,
+ COMMON.RX3_MAYBE,
+ COMMON.RX3_COMPLETABLE,
+ COMMON.RX3_OBSERVABLE,
+ COMMON.RX3_FLOWABLE,
+ COMMON.PUBLISHER,
+ )
)
) { invocation ->
val observable = invocation.processingEnv.requireTypeElement(rxTypeClassName)
@@ -791,7 +813,7 @@
Triple(COMMON.RX3_SINGLE, COMMON.RX3_ROOM, RxJava3TypeNames.SINGLE)
)
.forEach { (rxTypeSrc, _, rxTypeClassName) ->
- runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
val single = invocation.processingEnv.requireTypeElement(rxTypeClassName)
assertThat(single, notNullValue())
assertThat(
@@ -810,7 +832,7 @@
Triple(COMMON.RX3_MAYBE, COMMON.RX3_ROOM, RxJava3TypeNames.MAYBE)
)
.forEach { (rxTypeSrc, _, rxTypeClassName) ->
- runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
val maybe = invocation.processingEnv.requireTypeElement(rxTypeClassName)
assertThat(
RxCallableInsertOrUpsertMethodBinderProvider.getAll(invocation.context)
@@ -828,7 +850,7 @@
Triple(COMMON.RX3_COMPLETABLE, COMMON.RX3_ROOM, RxJava3TypeNames.COMPLETABLE)
)
.forEach { (rxTypeSrc, _, rxTypeClassName) ->
- runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
val completable = invocation.processingEnv.requireTypeElement(rxTypeClassName)
assertThat(
RxCallableInsertOrUpsertMethodBinderProvider.getAll(invocation.context)
@@ -841,7 +863,7 @@
@Test
fun testFindInsertListenableFuture() {
- runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
val future =
invocation.processingEnv.requireTypeElement(
GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE
@@ -856,7 +878,7 @@
@Test
fun testFindDeleteOrUpdateSingle() {
- runProcessorTest(sources = listOf(COMMON.RX2_SINGLE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.RX2_SINGLE)) { invocation ->
val single = invocation.processingEnv.requireTypeElement(RxJava2TypeNames.SINGLE)
assertThat(single, notNullValue())
assertThat(
@@ -870,7 +892,7 @@
@Test
fun testFindDeleteOrUpdateMaybe() {
- runProcessorTest(sources = listOf(COMMON.RX2_MAYBE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.RX2_MAYBE)) { invocation ->
val maybe = invocation.processingEnv.requireTypeElement(RxJava2TypeNames.MAYBE)
assertThat(maybe, notNullValue())
assertThat(
@@ -884,7 +906,7 @@
@Test
fun testFindDeleteOrUpdateCompletable() {
- runProcessorTest(sources = listOf(COMMON.RX2_COMPLETABLE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.RX2_COMPLETABLE)) { invocation ->
val completable =
invocation.processingEnv.requireTypeElement(RxJava2TypeNames.COMPLETABLE)
assertThat(completable, notNullValue())
@@ -899,7 +921,7 @@
@Test
fun testFindDeleteOrUpdateListenableFuture() {
- runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
val future =
invocation.processingEnv.requireTypeElement(
GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE
@@ -920,7 +942,7 @@
Triple(COMMON.RX3_SINGLE, COMMON.RX3_ROOM, RxJava3TypeNames.SINGLE)
)
.forEach { (rxTypeSrc, _, rxTypeClassName) ->
- runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
val single = invocation.processingEnv.requireTypeElement(rxTypeClassName)
assertThat(single).isNotNull()
assertThat(
@@ -939,7 +961,7 @@
Triple(COMMON.RX3_MAYBE, COMMON.RX3_ROOM, RxJava3TypeNames.MAYBE)
)
.forEach { (rxTypeSrc, _, rxTypeClassName) ->
- runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
val maybe = invocation.processingEnv.requireTypeElement(rxTypeClassName)
assertThat(
RxCallableInsertOrUpsertMethodBinderProvider.getAll(invocation.context)
@@ -957,7 +979,7 @@
Triple(COMMON.RX3_COMPLETABLE, COMMON.RX3_ROOM, RxJava3TypeNames.COMPLETABLE)
)
.forEach { (rxTypeSrc, _, rxTypeClassName) ->
- runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
val completable = invocation.processingEnv.requireTypeElement(rxTypeClassName)
assertThat(
RxCallableInsertOrUpsertMethodBinderProvider.getAll(invocation.context)
@@ -970,7 +992,7 @@
@Test
fun testFindUpsertListenableFuture() {
- runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
val future =
invocation.processingEnv.requireTypeElement(
GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE
@@ -985,7 +1007,7 @@
@Test
fun testFindLiveData() {
- runProcessorTest(sources = listOf(COMMON.COMPUTABLE_LIVE_DATA, COMMON.LIVE_DATA)) {
+ runProcessorTestWithK1(sources = listOf(COMMON.COMPUTABLE_LIVE_DATA, COMMON.LIVE_DATA)) {
invocation ->
val liveData =
invocation.processingEnv.requireTypeElement(LifecyclesTypeNames.LIVE_DATA)
@@ -999,7 +1021,7 @@
@Test
fun findPagingSourceIntKey() {
- runProcessorTest(
+ runProcessorTestWithK1(
sources = listOf(COMMON.LIMIT_OFFSET_PAGING_SOURCE),
) { invocation ->
val pagingSourceElement =
@@ -1143,7 +1165,8 @@
@Test
fun findListenableFuturePagingSourceJavaCollectionValue() {
- runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) {
+ invocation ->
val listenableFuturePagingSourceElement =
invocation.processingEnv.requireTypeElement(
PagingTypeNames.LISTENABLE_FUTURE_PAGING_SOURCE
@@ -1171,7 +1194,8 @@
@Test
fun findListenableFutureKotlinCollectionValue() {
- runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) {
+ invocation ->
val listenableFuturePagingSourceElement =
invocation.processingEnv.requireTypeElement(
PagingTypeNames.LISTENABLE_FUTURE_PAGING_SOURCE
@@ -1199,7 +1223,7 @@
@Test
fun findRx2PagingSourceJavaCollectionValue() {
- runProcessorTest(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
val rx2PagingSourceElement =
invocation.processingEnv.requireTypeElement(PagingTypeNames.RX2_PAGING_SOURCE)
val intType = invocation.processingEnv.requireType(Integer::class)
@@ -1225,7 +1249,7 @@
@Test
fun findRx2PagingSourceKotlinCollectionValue() {
- runProcessorTest(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
val rx2PagingSourceElement =
invocation.processingEnv.requireTypeElement(PagingTypeNames.RX2_PAGING_SOURCE)
val intType = invocation.processingEnv.requireType(Integer::class)
@@ -1251,7 +1275,7 @@
@Test
fun findRx3PagingSourceJavaCollectionValue() {
- runProcessorTest(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
val rx3PagingSourceElement =
invocation.processingEnv.requireTypeElement(PagingTypeNames.RX3_PAGING_SOURCE)
val intType = invocation.processingEnv.requireType(Integer::class)
@@ -1277,7 +1301,7 @@
@Test
fun findRx3PagingSourceKotlinCollectionValue() {
- runProcessorTest(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
val rx3PagingSourceElement =
invocation.processingEnv.requireTypeElement(PagingTypeNames.RX3_PAGING_SOURCE)
val intType = invocation.processingEnv.requireType(Integer::class)
@@ -1317,7 +1341,7 @@
"""
.trimIndent()
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources =
listOf(
inputSource,
@@ -1381,7 +1405,7 @@
"""
.trimIndent()
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources =
listOf(
inputSource,
@@ -1441,7 +1465,7 @@
"""
.trimIndent()
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources =
listOf(
inputSource,
@@ -1497,7 +1521,7 @@
"""
.trimIndent()
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources =
listOf(
inputSource,
@@ -1570,7 +1594,7 @@
@Test
fun findDataSourceFactory() {
- runProcessorTest(sources = listOf(COMMON.DATA_SOURCE_FACTORY)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(COMMON.DATA_SOURCE_FACTORY)) { invocation ->
val pagedListProvider =
invocation.processingEnv.requireTypeElement(PagingTypeNames.DATA_SOURCE_FACTORY)
assertThat(pagedListProvider, notNullValue())
@@ -1653,7 +1677,7 @@
"""
.trimIndent()
)
- runProcessorTest(sources = listOf(source)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(source)) { invocation ->
val converters =
CustomConverterProcessor(
context = invocation.context,
@@ -1789,7 +1813,7 @@
"""
.trimIndent()
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources =
listOf(
classExtendsClassWithEqualsAndHashcodeFunctions,
@@ -1832,7 +1856,7 @@
"""
.trimIndent()
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources =
listOf(
inputSource,
@@ -1864,7 +1888,7 @@
"""
.trimIndent()
)
- runProcessorTest(sources = listOf(source)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(source)) { invocation ->
val subjectTypeElement = invocation.processingEnv.requireTypeElement("Subject")
subjectTypeElement.getDeclaredFields().forEach {
@@ -1879,7 +1903,10 @@
val listOfInts = invocation.processingEnv.getDeclaredType(listElement, intType)
val intListConverter =
object :
- SingleStatementTypeConverter(listOfInts, invocation.context.COMMON_TYPES.STRING) {
+ SingleStatementTypeConverter(
+ listOfInts,
+ invocation.context.processingEnv.requireType(CommonTypeNames.STRING)
+ ) {
override fun buildStatement(inputVarName: String, scope: CodeGenScope): XCodeBlock {
return XCodeBlock.of(
scope.language,
@@ -1892,7 +1919,10 @@
val stringToIntListConverter =
object :
- SingleStatementTypeConverter(invocation.context.COMMON_TYPES.STRING, listOfInts) {
+ SingleStatementTypeConverter(
+ invocation.context.processingEnv.requireType(CommonTypeNames.STRING),
+ listOfInts
+ ) {
override fun buildStatement(inputVarName: String, scope: CodeGenScope): XCodeBlock {
return XCodeBlock.of(
scope.language,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt
index c02a5de..d67ad7d 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt
@@ -20,7 +20,7 @@
import androidx.room.compiler.processing.XVariableElement
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test
@@ -101,6 +101,6 @@
}
private fun runTest(handler: XTestInvocation.() -> Unit) {
- runProcessorTest(sources = listOf(TEST_OBJECT)) { it.apply { handler() } }
+ runProcessorTestWithK1(sources = listOf(TEST_OBJECT)) { it.apply { handler() } }
}
}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt
index 10e445c..12ca821 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt
@@ -19,8 +19,8 @@
import androidx.kruth.assertThat
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.processor.CustomConverterProcessor
+import androidx.room.runProcessorTestWithK1
import androidx.room.solver.types.CompositeTypeConverter
import androidx.room.solver.types.CustomTypeConverterWrapper
import androidx.room.solver.types.TypeConverter
@@ -65,7 +65,7 @@
"""
.trimIndent()
)
- runProcessorTest(sources = listOf(source)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(source)) { invocation ->
val convertersElm = invocation.processingEnv.requireTypeElement("MyConverters")
val converters = CustomConverterProcessor(invocation.context, convertersElm).process()
val store =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
index ba3efa3..6da36ab 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
@@ -22,10 +22,10 @@
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.ext.RoomTypeNames.ROOM_SQL_QUERY
import androidx.room.ext.RoomTypeNames.STRING_UTIL
import androidx.room.processor.QueryMethodProcessor
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.writer.QueryWriter
import org.junit.Test
@@ -361,7 +361,7 @@
fun singleQueryMethod(vararg input: String, handler: (Boolean, QueryWriter) -> Unit) {
val source =
Source.java("foo.bar.MyClass", DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX)
- runProcessorTest(sources = listOf(source)) { invocation ->
+ runProcessorTestWithK1(sources = listOf(source)) { invocation ->
val (owner, methods) =
invocation.roundEnv
.getElementsAnnotatedWith(Dao::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt
index 521e634..cdb11ac 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt
@@ -19,7 +19,7 @@
import androidx.kruth.assertThat
import androidx.room.compiler.processing.util.CompilationTestCapabilities
import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -58,7 +58,7 @@
}
var runCount = 0
- runProcessorTest(sources = listOf(source)) {
+ runProcessorTestWithK1(sources = listOf(source)) {
assertThat(it.processingEnv.findTypeElement("foo.bar.MyClass")).isNotNull()
runCount++
}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt b/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
index 7f3e54e..95c2eb0 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
@@ -31,8 +31,6 @@
import androidx.room.ext.KotlinTypeNames
import androidx.room.ext.LifecyclesTypeNames
import androidx.room.ext.ReactiveStreamsTypeNames
-import androidx.room.ext.RoomRxJava2TypeNames
-import androidx.room.ext.RoomRxJava3TypeNames
import androidx.room.ext.RxJava2TypeNames
import androidx.room.ext.RxJava3TypeNames
import androidx.room.processor.DatabaseViewProcessor
@@ -123,9 +121,7 @@
)
}
- val RX2_ROOM by lazy {
- loadJavaCode("common/input/Rx2Room.java", RoomRxJava2TypeNames.RX_ROOM.canonicalName)
- }
+ val RX2_ROOM by lazy { loadKotlinCode("common/input/Rx2Room.kt") }
val RX3_FLOWABLE by lazy {
loadJavaCode("common/input/rxjava3/Flowable.java", RxJava3TypeNames.FLOWABLE.canonicalName)
@@ -150,9 +146,7 @@
)
}
- val RX3_ROOM by lazy {
- loadJavaCode("common/input/Rx3Room.java", RoomRxJava3TypeNames.RX_ROOM.canonicalName)
- }
+ val RX3_ROOM by lazy { loadKotlinCode("common/input/Rx3Room.kt") }
val DATA_SOURCE_FACTORY by lazy { loadKotlinCode("common/input/DataSource.kt") }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt
index bd1705a..0cc8204 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt
@@ -24,6 +24,7 @@
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.ext.CommonTypeNames
import androidx.room.parser.Collate
import androidx.room.parser.SQLTypeAffinity
import androidx.room.parser.SqlParser
@@ -318,7 +319,9 @@
),
field(
"name",
- invocation.context.COMMON_TYPES.STRING,
+ invocation.context.processingEnv.requireType(
+ CommonTypeNames.STRING
+ ),
SQLTypeAffinity.TEXT,
defaultValue = "(NO_SUCH_CONSTANT)"
)
@@ -354,8 +357,16 @@
primitive(context, XTypeName.PRIMITIVE_INT),
SQLTypeAffinity.INTEGER
),
- field("name", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT),
- field("lastName", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT),
+ field(
+ "name",
+ context.processingEnv.requireType(CommonTypeNames.STRING),
+ SQLTypeAffinity.TEXT
+ ),
+ field(
+ "lastName",
+ context.processingEnv.requireType(CommonTypeNames.STRING),
+ SQLTypeAffinity.TEXT
+ ),
field(
"ratio",
primitive(context, XTypeName.PRIMITIVE_FLOAT),
@@ -372,7 +383,11 @@
primitive(context, XTypeName.PRIMITIVE_INT),
SQLTypeAffinity.INTEGER
),
- field("name", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT)
+ field(
+ "name",
+ context.processingEnv.requireType(CommonTypeNames.STRING),
+ SQLTypeAffinity.TEXT
+ )
)
)
)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
index 34b127f..b42941f 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
@@ -21,9 +21,9 @@
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.runJavaProcessorTest
-import androidx.room.compiler.processing.util.runKspTest
import androidx.room.migration.bundle.FieldBundle
import androidx.room.processor.Context
+import androidx.room.runKspTestWithK1
import androidx.room.util.SchemaDiffResult
import androidx.room.vo.AutoMigration
import loadTestSource
@@ -88,7 +88,7 @@
)
}
- runProcessorTest(listOf(specSource)) { invocation ->
+ runProcessorTestWithK1(listOf(specSource)) { invocation ->
val autoMigrationResultWithNewAddedColumn =
AutoMigration(
from = 1,
@@ -172,7 +172,7 @@
)
}
- runProcessorTest(listOf(specSource)) { invocation ->
+ runProcessorTestWithK1(listOf(specSource)) { invocation ->
val autoMigrationResultWithNewAddedColumn =
AutoMigration(
from = 1,
@@ -264,7 +264,7 @@
)
}
- runProcessorTest(listOf(specSource)) { invocation ->
+ runProcessorTestWithK1(listOf(specSource)) { invocation ->
val autoMigrationResultWithNewAddedColumn =
AutoMigration(
from = 1,
@@ -320,7 +320,7 @@
}
}
- private fun runProcessorTest(sources: List<Source>, handler: (XTestInvocation) -> Unit) {
+ private fun runProcessorTestWithK1(sources: List<Source>, handler: (XTestInvocation) -> Unit) {
when (codeLanguage) {
CodeLanguage.JAVA ->
runJavaProcessorTest(
@@ -330,7 +330,7 @@
handler = handler
)
CodeLanguage.KOTLIN ->
- runKspTest(
+ runKspTestWithK1(
sources = sources + kotlinDatabaseSource,
options =
mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/BaseDaoKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/BaseDaoKotlinCodeGenTest.kt
index 8385ac1..d30d126 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/BaseDaoKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/BaseDaoKotlinCodeGenTest.kt
@@ -19,11 +19,10 @@
import androidx.room.DatabaseProcessingStep
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runKspTest
import androidx.room.processor.Context
+import androidx.room.runKspTestWithK1
import java.io.File
import loadTestSource
-import org.jetbrains.kotlin.config.JvmDefaultMode
import writeTestSource
abstract class BaseDaoKotlinCodeGenTest {
@@ -35,14 +34,14 @@
sources: List<Source>,
expectedFilePath: String,
compiledFiles: List<File> = emptyList(),
- jvmDefaultMode: JvmDefaultMode = JvmDefaultMode.DEFAULT,
+ jvmDefaultMode: String = "disable",
handler: (XTestInvocation) -> Unit = {}
) {
- runKspTest(
+ runKspTestWithK1(
sources = sources,
classpath = compiledFiles,
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
- kotlincArguments = listOf("-Xjvm-default=${jvmDefaultMode.description}")
+ kotlincArguments = listOf("-Xjvm-default=${jvmDefaultMode}")
) {
val databaseFqn = "androidx.room.Database"
DatabaseProcessingStep()
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
index 02646fd..a4afa61 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
@@ -21,7 +21,6 @@
import androidx.room.compiler.processing.util.compileFiles
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
-import org.jetbrains.kotlin.config.JvmDefaultMode
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -1033,8 +1032,7 @@
@Test
fun delegatingFunctions_defaultImplBridge(
- @TestParameter("DISABLE", "ALL_COMPATIBILITY", "ALL_INCOMPATIBLE")
- jvmDefaultMode: JvmDefaultMode
+ @TestParameter("disable", "all-compatibility", "all") jvmDefaultMode: String
) {
// For parametrized tests, use method name from reflection
val testName = object {}.javaClass.enclosingMethod!!.name
@@ -1110,8 +1108,7 @@
@Test
fun transactionMethodAdapter_interface(
- @TestParameter("DISABLE", "ALL_COMPATIBILITY", "ALL_INCOMPATIBLE")
- jvmDefaultMode: JvmDefaultMode
+ @TestParameter("disable", "all-compatibility", "all") jvmDefaultMode: String
) {
// For parametrized tests, use method name from reflection
val testName = object {}.javaClass.enclosingMethod!!.name
@@ -1982,18 +1979,6 @@
@Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
fun getMaybe(vararg arg: String?): Maybe<MyEntity>
-
- @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
- fun getFlowableNullable(vararg arg: String?): Flowable<MyEntity?>
-
- @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
- fun getObservableNullable(vararg arg: String?): Observable<MyEntity?>
-
- @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
- fun getSingleNullable(vararg arg: String?): Single<MyEntity?>
-
- @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
- fun getMaybeNullable(vararg arg: String?): Maybe<MyEntity?>
}
@Entity
@@ -2010,13 +1995,19 @@
listOf(
src,
databaseSrc,
- COMMON.RX2_ROOM,
- COMMON.RX2_FLOWABLE,
- COMMON.RX2_OBSERVABLE,
- COMMON.RX2_SINGLE,
- COMMON.RX2_MAYBE,
- COMMON.PUBLISHER,
- COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION
+ ),
+ compiledFiles =
+ compileFiles(
+ listOf(
+ COMMON.RX2_ROOM,
+ COMMON.RX2_FLOWABLE,
+ COMMON.RX2_OBSERVABLE,
+ COMMON.RX2_SINGLE,
+ COMMON.RX2_MAYBE,
+ COMMON.RX2_COMPLETABLE,
+ COMMON.PUBLISHER,
+ COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION
+ )
),
expectedFilePath = getTestGoldenPath(testName.methodName)
)
@@ -2044,18 +2035,6 @@
@Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
fun getMaybe(vararg arg: String?): Maybe<MyEntity>
-
- @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
- fun getFlowableNullable(vararg arg: String?): Flowable<MyEntity?>
-
- @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
- fun getObservableNullable(vararg arg: String?): Observable<MyEntity?>
-
- @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
- fun getSingleNullable(vararg arg: String?): Single<MyEntity?>
-
- @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
- fun getMaybeNullable(vararg arg: String?): Maybe<MyEntity?>
}
@Entity
@@ -2072,13 +2051,19 @@
listOf(
src,
databaseSrc,
- COMMON.RX3_ROOM,
- COMMON.RX3_FLOWABLE,
- COMMON.RX3_OBSERVABLE,
- COMMON.RX3_SINGLE,
- COMMON.RX3_MAYBE,
- COMMON.PUBLISHER,
- COMMON.RX3_EMPTY_RESULT_SET_EXCEPTION
+ ),
+ compiledFiles =
+ compileFiles(
+ listOf(
+ COMMON.RX3_ROOM,
+ COMMON.RX3_FLOWABLE,
+ COMMON.RX3_OBSERVABLE,
+ COMMON.RX3_SINGLE,
+ COMMON.RX3_MAYBE,
+ COMMON.RX3_COMPLETABLE,
+ COMMON.PUBLISHER,
+ COMMON.RX3_EMPTY_RESULT_SET_EXCEPTION
+ )
),
expectedFilePath = getTestGoldenPath(testName.methodName)
)
@@ -2119,14 +2104,19 @@
listOf(
src,
databaseSrc,
- COMMON.RX2_ROOM,
- COMMON.RX2_FLOWABLE,
- COMMON.RX2_OBSERVABLE,
- COMMON.RX2_SINGLE,
- COMMON.RX2_MAYBE,
- COMMON.RX2_COMPLETABLE,
- COMMON.PUBLISHER,
- COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION
+ ),
+ compiledFiles =
+ compileFiles(
+ listOf(
+ COMMON.RX2_ROOM,
+ COMMON.RX2_FLOWABLE,
+ COMMON.RX2_OBSERVABLE,
+ COMMON.RX2_SINGLE,
+ COMMON.RX2_MAYBE,
+ COMMON.RX2_COMPLETABLE,
+ COMMON.PUBLISHER,
+ COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION
+ )
),
expectedFilePath = getTestGoldenPath(testName.methodName)
)
@@ -2167,14 +2157,19 @@
listOf(
src,
databaseSrc,
- COMMON.RX3_ROOM,
- COMMON.RX3_FLOWABLE,
- COMMON.RX3_OBSERVABLE,
- COMMON.RX3_SINGLE,
- COMMON.RX3_MAYBE,
- COMMON.RX3_COMPLETABLE,
- COMMON.PUBLISHER,
- COMMON.RX3_EMPTY_RESULT_SET_EXCEPTION
+ ),
+ compiledFiles =
+ compileFiles(
+ listOf(
+ COMMON.RX3_ROOM,
+ COMMON.RX3_FLOWABLE,
+ COMMON.RX3_OBSERVABLE,
+ COMMON.RX3_SINGLE,
+ COMMON.RX3_MAYBE,
+ COMMON.RX3_COMPLETABLE,
+ COMMON.PUBLISHER,
+ COMMON.RX3_EMPTY_RESULT_SET_EXCEPTION
+ )
),
expectedFilePath = getTestGoldenPath(testName.methodName)
)
@@ -2286,13 +2281,19 @@
.trimIndent()
)
runTest(
- sources =
- listOf(
- src,
- databaseSrc,
- COMMON.RX2_SINGLE,
- COMMON.RX2_COMPLETABLE,
- COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION,
+ sources = listOf(src, databaseSrc),
+ compiledFiles =
+ compileFiles(
+ listOf(
+ COMMON.RX2_ROOM,
+ COMMON.RX2_SINGLE,
+ COMMON.RX2_MAYBE,
+ COMMON.RX2_COMPLETABLE,
+ COMMON.RX2_FLOWABLE,
+ COMMON.RX2_OBSERVABLE,
+ COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION,
+ COMMON.PUBLISHER,
+ )
),
expectedFilePath = getTestGoldenPath(testName.methodName)
)
@@ -2344,13 +2345,19 @@
.trimIndent()
)
runTest(
- sources =
- listOf(
- src,
- databaseSrc,
- COMMON.RX3_SINGLE,
- COMMON.RX3_COMPLETABLE,
- COMMON.RX3_EMPTY_RESULT_SET_EXCEPTION
+ sources = listOf(src, databaseSrc),
+ compiledFiles =
+ compileFiles(
+ listOf(
+ COMMON.RX3_ROOM,
+ COMMON.RX3_SINGLE,
+ COMMON.RX3_MAYBE,
+ COMMON.RX3_COMPLETABLE,
+ COMMON.RX3_FLOWABLE,
+ COMMON.RX3_OBSERVABLE,
+ COMMON.RX3_EMPTY_RESULT_SET_EXCEPTION,
+ COMMON.PUBLISHER,
+ )
),
expectedFilePath = getTestGoldenPath(testName.methodName)
)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt
index 9679f86..3f13507 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt
@@ -23,9 +23,9 @@
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.ext.RoomTypeNames.ROOM_DB
import androidx.room.processor.DaoProcessor
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
@@ -148,10 +148,10 @@
COMMON.PUBLISHER
)
)
- runProcessorTest(sources = sources, classpath = libs) { invocation ->
+ runProcessorTestWithK1(sources = sources, classpath = libs) { invocation ->
if (invocation.isKsp && !javaLambdaSyntaxAvailable) {
// Skip KSP backend without lambda syntax, it is a nonsensical combination.
- return@runProcessorTest
+ return@runProcessorTestWithK1
}
val dao =
invocation.roundEnv
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseKotlinCodeGenTest.kt
index b13bd03..b3bc884 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseKotlinCodeGenTest.kt
@@ -19,8 +19,8 @@
import androidx.room.DatabaseProcessingStep
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runKspTest
import androidx.room.processor.Context
+import androidx.room.runKspTestWithK1
import loadTestSource
import org.junit.Rule
import org.junit.Test
@@ -245,7 +245,7 @@
expectedFilePath: String,
handler: (XTestInvocation) -> Unit = {}
) {
- runKspTest(
+ runKspTestWithK1(
sources = sources,
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
) {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
index c912440..5eaf944 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
@@ -19,8 +19,8 @@
import androidx.room.DatabaseProcessingStep
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runKspTest
import androidx.room.processor.Context
+import androidx.room.runKspTestWithK1
import loadTestSource
import org.junit.Rule
import org.junit.Test
@@ -72,7 +72,7 @@
expectedFilePath: String,
handler: (XTestInvocation) -> Unit = {}
) {
- runKspTest(
+ runKspTestWithK1(
sources = sources,
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
) {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt
index eb077c0..2632fe5 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt
@@ -22,8 +22,8 @@
import androidx.room.compiler.processing.util.CompilationResultSubject
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.processor.Context
+import androidx.room.runProcessorTestWithK1
import androidx.testutils.generateAllEnumerations
import loadTestSource
import org.junit.Test
@@ -159,7 +159,7 @@
COMMON.LISTENABLE_FUTURE
)
)
- runProcessorTest(
+ runProcessorTestWithK1(
sources = sources,
classpath = libs,
options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt
index 68ca39f..b237147 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt
@@ -21,13 +21,12 @@
import androidx.room.compiler.processing.XProcessingEnv
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.ext.RoomTypeNames.ROOM_DB
import androidx.room.processor.DaoProcessor
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import com.google.common.truth.StringSubject
import createVerifierFromEntitiesAndViews
-import org.jetbrains.kotlin.config.JvmDefaultMode
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -42,11 +41,11 @@
* For Java default method tests, we have DefaultDaoMethodsTest in TestApp.
*/
@RunWith(Parameterized::class)
-class DefaultsInDaoTest(private val jvmDefaultMode: JvmDefaultMode) {
+class DefaultsInDaoTest(private val jvmDefaultMode: String) {
@Test
fun abstractDao() {
val defaultWithCompatibilityAnnotation =
- if (jvmDefaultMode == JvmDefaultMode.ALL_COMPATIBILITY) {
+ if (jvmDefaultMode == "all-compatibility") {
"@JvmDefaultWithoutCompatibility"
} else {
""
@@ -101,7 +100,7 @@
)
compileInEachDefaultsMode(source) { generated ->
generated.contains("public void upsert(final User obj)")
- if (jvmDefaultMode == JvmDefaultMode.DISABLE) {
+ if (jvmDefaultMode == "disable") {
generated.contains("SubjectDao.DefaultImpls.upsert(SubjectDao_Impl.this")
} else {
generated.contains("SubjectDao.super.upsert(")
@@ -137,7 +136,7 @@
"public Object upsert(final User obj, " +
"final Continuation<? super Unit> \$completion)"
)
- if (jvmDefaultMode == JvmDefaultMode.DISABLE) {
+ if (jvmDefaultMode == "disable") {
generated.contains("SubjectDao.DefaultImpls.upsert(SubjectDao_Impl.this")
} else {
generated.contains("SubjectDao.super.upsert(")
@@ -179,11 +178,10 @@
jvmTarget: String = "1.8",
handler: (StringSubject) -> Unit
) {
- runProcessorTest(
+ runProcessorTestWithK1(
sources = listOf(source, COMMON.COROUTINES_ROOM, COMMON.ROOM_DATABASE_KTX),
javacArguments = listOf("-source", jvmTarget),
- kotlincArguments =
- listOf("-jvm-target=$jvmTarget", "-Xjvm-default=${jvmDefaultMode.description}")
+ kotlincArguments = listOf("-jvm-target=$jvmTarget", "-Xjvm-default=${jvmDefaultMode}")
) { invocation ->
invocation.roundEnv
.getElementsAnnotatedWith(androidx.room.Dao::class.qualifiedName!!)
@@ -223,9 +221,9 @@
@Parameters(name = "jvmDefaultMode={0}")
fun modes() =
listOf(
- JvmDefaultMode.ALL_COMPATIBILITY,
- JvmDefaultMode.ALL_INCOMPATIBLE,
- JvmDefaultMode.DISABLE,
+ "all-compatibility",
+ "all",
+ "disable",
)
}
}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/OpenDelegateWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/OpenDelegateWriterTest.kt
index 5e9aa20..26f7d0b 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/OpenDelegateWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/OpenDelegateWriterTest.kt
@@ -19,8 +19,8 @@
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.processor.DatabaseProcessor
+import androidx.room.runProcessorTestWithK1
import androidx.room.testing.context
import androidx.room.vo.Database
import org.hamcrest.CoreMatchers.`is`
@@ -219,7 +219,7 @@
}
"""
)
- runProcessorTest(sources = sources + databaseCode) { invocation ->
+ runProcessorTestWithK1(sources = sources + databaseCode) { invocation ->
val db =
invocation.roundEnv
.getElementsAnnotatedWith(androidx.room.Database::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/test-data/common/input/Rx2Room.java b/room/room-compiler/src/test/test-data/common/input/Rx2Room.java
deleted file mode 100644
index 9e06920..0000000
--- a/room/room-compiler/src/test/test-data/common/input/Rx2Room.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// mock rx2 helper
-package androidx.room;
-
-import androidx.room.RoomDatabase;
-import java.util.concurrent.Callable;
-import io.reactivex.Flowable;
-import io.reactivex.Observable;
-import io.reactivex.Single;
-public class RxRoom {
- public static <T> Flowable<T> createFlowable(final RoomDatabase database,
- final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
- return null;
- }
-
- public static <T> Observable<T> createObservable(final RoomDatabase database,
- final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
- return null;
- }
-
- public static <T> Single<T> createSingle(final Callable<? extends T> callable) {
- return null;
- }
-}
diff --git a/room/room-compiler/src/test/test-data/common/input/Rx2Room.kt b/room/room-compiler/src/test/test-data/common/input/Rx2Room.kt
new file mode 100644
index 0000000..771a7c1
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/common/input/Rx2Room.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room
+
+import androidx.sqlite.SQLiteConnection
+import io.reactivex.Completable
+import io.reactivex.Flowable
+import io.reactivex.Maybe
+import io.reactivex.Observable
+import io.reactivex.Single
+import java.util.concurrent.Callable
+
+// mock rx2 helper
+ class RxRoom {
+
+ companion object {
+
+ @JvmField
+ val NOTHING: Any = Any()
+
+ @JvmStatic
+ fun <T : Any> createFlowable(
+ db: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ block: (SQLiteConnection) -> T?
+ ): Flowable<T> {
+ TODO()
+ }
+
+ @JvmStatic
+ fun <T : Any> createObservable(
+ db: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ block: (SQLiteConnection) -> T?
+ ): Observable<T> {
+ TODO()
+ }
+
+ @JvmStatic
+ fun <T : Any> createMaybe(
+ db: RoomDatabase,
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> T?
+ ): Maybe<T> {
+ TODO()
+ }
+
+ @JvmStatic
+ fun createCompletable(
+ db: RoomDatabase,
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> Unit
+ ): Completable {
+ TODO()
+ }
+
+ @JvmStatic
+ fun <T : Any> createSingle(
+ db: RoomDatabase,
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> T?
+ ): Single<T> {
+ TODO()
+ }
+
+ @JvmStatic
+ fun createFlowable(database: RoomDatabase, vararg tableNames: String): Flowable<Any> {
+ TODO()
+ }
+
+ @JvmStatic
+ fun createObservable(database: RoomDatabase, vararg tableNames: String): Observable<Any> {
+ TODO()
+ }
+ }
+}
diff --git a/room/room-compiler/src/test/test-data/common/input/Rx3Room.java b/room/room-compiler/src/test/test-data/common/input/Rx3Room.java
deleted file mode 100644
index 518609d..0000000
--- a/room/room-compiler/src/test/test-data/common/input/Rx3Room.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// mock rx3 helper
-package androidx.room.rxjava3;
-
-import androidx.room.RoomDatabase;
-import java.util.concurrent.Callable;
-import io.reactivex.rxjava3.core.Flowable;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Single;
-
-public class RxRoom {
- public static <T> Flowable<T> createFlowable(final RoomDatabase database,
- final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
- return null;
- }
-
- public static <T> Observable<T> createObservable(final RoomDatabase database,
- final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
- return null;
- }
-
- public static <T> Single<T> createSingle(final Callable<? extends T> callable) {
- return null;
- }
-}
diff --git a/room/room-compiler/src/test/test-data/common/input/Rx3Room.kt b/room/room-compiler/src/test/test-data/common/input/Rx3Room.kt
new file mode 100644
index 0000000..663e479
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/common/input/Rx3Room.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// mock rx2 helper
+@file:JvmName("RxRoom")
+
+package androidx.room.rxjava3
+
+import androidx.room.RoomDatabase
+import androidx.sqlite.SQLiteConnection
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Flowable
+import io.reactivex.rxjava3.core.Maybe
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Single
+import java.util.concurrent.Callable
+
+class Rx3RoomArtifactMarker private constructor()
+
+@JvmField
+val NOTHING: Any = Any()
+
+fun <T : Any> createFlowable(
+ db: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ block: (SQLiteConnection) -> T?
+): Flowable<T> {
+ TODO()
+}
+
+fun <T : Any> createObservable(
+ db: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ block: (SQLiteConnection) -> T?
+): Observable<T> {
+ TODO()
+}
+
+fun <T : Any> createMaybe(
+ db: RoomDatabase,
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> T?
+): Maybe<T> {
+ TODO()
+}
+
+fun createCompletable(
+ db: RoomDatabase,
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> Unit
+): Completable {
+ TODO()
+}
+
+fun <T : Any> createSingle(
+ db: RoomDatabase,
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> T?
+): Single<T> {
+ TODO()
+}
+
+fun createFlowable(
+ database: RoomDatabase,
+ vararg tableNames: String
+): Flowable<Any> {
+ TODO()
+}
+
+fun <T : Any> createFlowable(
+ database: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ callable: Callable<out T>
+): Flowable<T> {
+ TODO()
+}
+
+fun createObservable(
+ database: RoomDatabase,
+ vararg tableNames: String
+): Observable<Any> {
+ TODO()
+}
+
+fun <T : Any> createObservable(
+ database: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ callable: Callable<out T>
+): Observable<T> {
+ TODO()
+}
+
+fun <T : Any> createSingle(callable: Callable<out T>): Single<T> {
+ TODO()
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java
index 0a79ba3..0fa55d4 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java
@@ -2,10 +2,8 @@
import android.database.Cursor;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.room.RoomDatabase;
-import androidx.room.RoomSQLiteQuery;
import androidx.room.guava.GuavaRoom;
import androidx.room.util.CursorUtil;
import androidx.room.util.DBUtil;
@@ -15,7 +13,6 @@
import androidx.sqlite.db.SupportSQLiteQuery;
import com.google.common.util.concurrent.ListenableFuture;
import java.lang.Class;
-import java.lang.Exception;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
@@ -24,7 +21,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Callable;
import javax.annotation.processing.Generated;
@Generated("androidx.room.RoomProcessor")
@@ -387,48 +383,38 @@
@Override
public LiveData<User> getByIdLive(final int id) {
final String _sql = "SELECT * FROM user where uid = ?";
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
- int _argIndex = 1;
- _statement.bindLong(_argIndex, id);
- return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<User>() {
- @Override
- @Nullable
- public User call() throws Exception {
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
- final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
- final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
- final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
- final User _result;
- if (_cursor.moveToFirst()) {
- _result = new User();
- _result.uid = _cursor.getInt(_cursorIndexOfUid);
- if (_cursor.isNull(_cursorIndexOfName)) {
- _result.name = null;
- } else {
- _result.name = _cursor.getString(_cursorIndexOfName);
- }
- final String _tmpLastName;
- if (_cursor.isNull(_cursorIndexOfLastName)) {
- _tmpLastName = null;
- } else {
- _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
- }
- _result.setLastName(_tmpLastName);
- _result.age = _cursor.getInt(_cursorIndexOfAge);
+ return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, id);
+ final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+ final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+ final User _result;
+ if (_stmt.step()) {
+ _result = new User();
+ _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+ if (_stmt.isNull(_cursorIndexOfName)) {
+ _result.name = null;
} else {
- _result = null;
+ _result.name = _stmt.getText(_cursorIndexOfName);
}
- return _result;
- } finally {
- _cursor.close();
+ final String _tmpLastName;
+ if (_stmt.isNull(_cursorIndexOfLastName)) {
+ _tmpLastName = null;
+ } else {
+ _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+ }
+ _result.setLastName(_tmpLastName);
+ _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+ } else {
+ _result = null;
}
- }
-
- @Override
- protected void finalize() {
- _statement.release();
+ return _result;
+ } finally {
+ _stmt.close();
}
});
}
@@ -441,56 +427,45 @@
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
_stringBuilder.append(")");
final String _sql = _stringBuilder.toString();
- final int _argCount = 0 + _inputSize;
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
- int _argIndex = 1;
- if (ids == null) {
- _statement.bindNull(_argIndex);
- } else {
- for (int _item : ids) {
- _statement.bindLong(_argIndex, _item);
- _argIndex++;
- }
- }
- return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<List<User>>() {
- @Override
- @Nullable
- public List<User> call() throws Exception {
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
- final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
- final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
- final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
- final List<User> _result = new ArrayList<User>();
- while (_cursor.moveToNext()) {
- final User _item_1;
- _item_1 = new User();
- _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
- if (_cursor.isNull(_cursorIndexOfName)) {
- _item_1.name = null;
- } else {
- _item_1.name = _cursor.getString(_cursorIndexOfName);
- }
- final String _tmpLastName;
- if (_cursor.isNull(_cursorIndexOfLastName)) {
- _tmpLastName = null;
- } else {
- _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
- }
- _item_1.setLastName(_tmpLastName);
- _item_1.age = _cursor.getInt(_cursorIndexOfAge);
- _result.add(_item_1);
+ return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ if (ids == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (int _item : ids) {
+ _stmt.bindLong(_argIndex, _item);
+ _argIndex++;
}
- return _result;
- } finally {
- _cursor.close();
}
- }
-
- @Override
- protected void finalize() {
- _statement.release();
+ final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+ final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+ final List<User> _result = new ArrayList<User>();
+ while (_stmt.step()) {
+ final User _item_1;
+ _item_1 = new User();
+ _item_1.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+ if (_stmt.isNull(_cursorIndexOfName)) {
+ _item_1.name = null;
+ } else {
+ _item_1.name = _stmt.getText(_cursorIndexOfName);
+ }
+ final String _tmpLastName;
+ if (_stmt.isNull(_cursorIndexOfLastName)) {
+ _tmpLastName = null;
+ } else {
+ _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+ }
+ _item_1.setLastName(_tmpLastName);
+ _item_1.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+ _result.add(_item_1);
+ }
+ return _result;
+ } finally {
+ _stmt.close();
}
});
}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/DeletionDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/DeletionDao.java
index 996eaf5..12f1fc8 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/DeletionDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/DeletionDao.java
@@ -1,30 +1,26 @@
package foo.bar;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityDeletionOrUpdateAdapter;
import androidx.room.RoomDatabase;
+import androidx.room.RxRoom;
import androidx.room.util.DBUtil;
import androidx.room.util.SQLiteConnectionUtil;
import androidx.room.util.StringUtil;
import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteStatement;
import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Single;
import java.lang.Class;
-import java.lang.Exception;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.lang.SuppressWarnings;
-import java.lang.Void;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Callable;
import javax.annotation.processing.Generated;
+import kotlin.Unit;
@Generated("androidx.room.RoomProcessor")
@SuppressWarnings({"unchecked", "deprecation", "removal"})
@@ -33,8 +29,6 @@
private final EntityDeleteOrUpdateAdapter<User> __deleteAdapterOfUser;
- private final EntityDeletionOrUpdateAdapter<User> __deleteCompatAdapterOfUser;
-
private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
private final EntityDeleteOrUpdateAdapter<Book> __deleteAdapterOfBook;
@@ -53,18 +47,6 @@
statement.bindLong(1, entity.uid);
}
};
- this.__deleteCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
- @Override
- @NonNull
- protected String createQuery() {
- return "DELETE FROM `User` WHERE `uid` = ?";
- }
-
- @Override
- protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
- statement.bindLong(1, entity.uid);
- }
- };
this.__deleteAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
@Override
@NonNull
@@ -164,57 +146,27 @@
@Override
public Completable deleteUserCompletable(final User user) {
- return Completable.fromCallable(new Callable<Void>() {
- @Override
- @Nullable
- public Void call() throws Exception {
- __db.beginTransaction();
- try {
- __deleteCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return null;
- } finally {
- __db.endTransaction();
- }
- }
+ return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+ __deleteAdapterOfUser.handle(_connection, user);
+ return Unit.INSTANCE;
});
}
@Override
public Single<Integer> deleteUserSingle(final User user) {
- return Single.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- int _total = 0;
- __db.beginTransaction();
- try {
- _total += __deleteCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return _total;
- } finally {
- __db.endTransaction();
- }
- }
+ return RxRoom.createSingle(__db, false, true, (_connection) -> {
+ int _result = 0;
+ _result += __deleteAdapterOfUser.handle(_connection, user);
+ return _result;
});
}
@Override
public Maybe<Integer> deleteUserMaybe(final User user) {
- return Maybe.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- int _total = 0;
- __db.beginTransaction();
- try {
- _total += __deleteCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return _total;
- } finally {
- __db.endTransaction();
- }
- }
+ return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+ int _result = 0;
+ _result += __deleteAdapterOfUser.handle(_connection, user);
+ return _result;
});
}
@@ -254,66 +206,48 @@
@Override
public Completable deleteByUidCompletable(final int uid) {
- return Completable.fromCallable(new Callable<Void>() {
- @Override
- @Nullable
- public Void call() throws Exception {
- final String _sql = "DELETE FROM user where uid = ?";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+ final String _sql = "DELETE FROM user where uid = ?";
+ return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
int _argIndex = 1;
_stmt.bindLong(_argIndex, uid);
- __db.beginTransaction();
- try {
- _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return null;
- } finally {
- __db.endTransaction();
- }
+ _stmt.step();
+ return Unit.INSTANCE;
+ } finally {
+ _stmt.close();
}
});
}
@Override
public Single<Integer> deleteByUidSingle(final int uid) {
- return Single.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- final String _sql = "DELETE FROM user where uid = ?";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+ final String _sql = "DELETE FROM user where uid = ?";
+ return RxRoom.createSingle(__db, false, true, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
int _argIndex = 1;
_stmt.bindLong(_argIndex, uid);
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+ } finally {
+ _stmt.close();
}
});
}
@Override
public Maybe<Integer> deleteByUidMaybe(final int uid) {
- return Maybe.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- final String _sql = "DELETE FROM user where uid = ?";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+ final String _sql = "DELETE FROM user where uid = ?";
+ return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
int _argIndex = 1;
_stmt.bindLong(_argIndex, uid);
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+ } finally {
+ _stmt.close();
}
});
}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/UpdateDao.java
index 6214715..4f809d6 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/UpdateDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/UpdateDao.java
@@ -1,27 +1,24 @@
package foo.bar;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityDeletionOrUpdateAdapter;
import androidx.room.RoomDatabase;
+import androidx.room.RxRoom;
import androidx.room.util.DBUtil;
+import androidx.room.util.SQLiteConnectionUtil;
import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteStatement;
import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Single;
import java.lang.Class;
-import java.lang.Exception;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
import java.lang.SuppressWarnings;
-import java.lang.Void;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Callable;
import javax.annotation.processing.Generated;
+import kotlin.Unit;
@Generated("androidx.room.RoomProcessor")
@SuppressWarnings({"unchecked", "deprecation", "removal"})
@@ -32,8 +29,6 @@
private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser_1;
- private final EntityDeletionOrUpdateAdapter<User> __updateCompatAdapterOfUser;
-
private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
private final EntityDeleteOrUpdateAdapter<Book> __updateAdapterOfBook;
@@ -88,30 +83,6 @@
statement.bindLong(5, entity.uid);
}
};
- this.__updateCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
- @Override
- @NonNull
- protected String createQuery() {
- return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
- }
-
- @Override
- protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
- statement.bindLong(1, entity.uid);
- if (entity.name == null) {
- statement.bindNull(2);
- } else {
- statement.bindString(2, entity.name);
- }
- if (entity.getLastName() == null) {
- statement.bindNull(3);
- } else {
- statement.bindString(3, entity.getLastName());
- }
- statement.bindLong(4, entity.age);
- statement.bindLong(5, entity.uid);
- }
- };
this.__updateAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
@Override
@NonNull
@@ -232,57 +203,27 @@
@Override
public Completable updateUserAndReturnCountCompletable(final User user) {
- return Completable.fromCallable(new Callable<Void>() {
- @Override
- @Nullable
- public Void call() throws Exception {
- __db.beginTransaction();
- try {
- __updateCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return null;
- } finally {
- __db.endTransaction();
- }
- }
+ return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+ __updateAdapterOfUser.handle(_connection, user);
+ return Unit.INSTANCE;
});
}
@Override
public Single<Integer> updateUserAndReturnCountSingle(final User user) {
- return Single.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- int _total = 0;
- __db.beginTransaction();
- try {
- _total += __updateCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return _total;
- } finally {
- __db.endTransaction();
- }
- }
+ return RxRoom.createSingle(__db, false, true, (_connection) -> {
+ int _result = 0;
+ _result += __updateAdapterOfUser.handle(_connection, user);
+ return _result;
});
}
@Override
public Maybe<Integer> updateUserAndReturnCountMaybe(final User user) {
- return Maybe.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- int _total = 0;
- __db.beginTransaction();
- try {
- _total += __updateCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return _total;
- } finally {
- __db.endTransaction();
- }
- }
+ return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+ int _result = 0;
+ _result += __updateAdapterOfUser.handle(_connection, user);
+ return _result;
});
}
@@ -340,60 +281,42 @@
@Override
public Completable ageUserAllCompletable() {
- return Completable.fromCallable(new Callable<Void>() {
- @Override
- @Nullable
- public Void call() throws Exception {
- final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
- __db.beginTransaction();
- try {
- _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return null;
- } finally {
- __db.endTransaction();
- }
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ _stmt.step();
+ return Unit.INSTANCE;
+ } finally {
+ _stmt.close();
}
});
}
@Override
public Single<Integer> ageUserAllSingle() {
- return Single.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ return RxRoom.createSingle(__db, false, true, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+ } finally {
+ _stmt.close();
}
});
}
@Override
public Maybe<Integer> ageUserAllMaybe() {
- return Maybe.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+ } finally {
+ _stmt.close();
}
});
}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java
index 27a0bc1..8074e12 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java
@@ -5,7 +5,6 @@
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.room.RoomDatabase;
-import androidx.room.RoomSQLiteQuery;
import androidx.room.guava.GuavaRoom;
import androidx.room.util.CursorUtil;
import androidx.room.util.DBUtil;
@@ -17,7 +16,6 @@
import com.google.common.util.concurrent.ListenableFuture;
import java.lang.Boolean;
import java.lang.Class;
-import java.lang.Exception;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
@@ -26,7 +24,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Callable;
import javax.annotation.processing.Generated;
import kotlin.jvm.functions.Function1;
@@ -426,49 +423,43 @@
@Override
public LiveData<User> getByIdLive(final int id) {
final String _sql = "SELECT * FROM user where uid = ?";
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
- int _argIndex = 1;
- _statement.bindLong(_argIndex, id);
- return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<User>() {
+ return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Function1<SQLiteConnection, User>() {
@Override
@Nullable
- public User call() throws Exception {
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
+ public User invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
try {
- final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
- final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
- final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
- final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, id);
+ final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+ final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
final User _result;
- if (_cursor.moveToFirst()) {
+ if (_stmt.step()) {
_result = new User();
- _result.uid = _cursor.getInt(_cursorIndexOfUid);
- if (_cursor.isNull(_cursorIndexOfName)) {
+ _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+ if (_stmt.isNull(_cursorIndexOfName)) {
_result.name = null;
} else {
- _result.name = _cursor.getString(_cursorIndexOfName);
+ _result.name = _stmt.getText(_cursorIndexOfName);
}
final String _tmpLastName;
- if (_cursor.isNull(_cursorIndexOfLastName)) {
+ if (_stmt.isNull(_cursorIndexOfLastName)) {
_tmpLastName = null;
} else {
- _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+ _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
}
_result.setLastName(_tmpLastName);
- _result.age = _cursor.getInt(_cursorIndexOfAge);
+ _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
} else {
_result = null;
}
return _result;
} finally {
- _cursor.close();
+ _stmt.close();
}
}
-
- @Override
- protected void finalize() {
- _statement.release();
- }
});
}
@@ -480,57 +471,50 @@
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
_stringBuilder.append(")");
final String _sql = _stringBuilder.toString();
- final int _argCount = 0 + _inputSize;
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
- int _argIndex = 1;
- if (ids == null) {
- _statement.bindNull(_argIndex);
- } else {
- for (int _item : ids) {
- _statement.bindLong(_argIndex, _item);
- _argIndex++;
- }
- }
- return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<List<User>>() {
+ return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Function1<SQLiteConnection, List<User>>() {
@Override
@Nullable
- public List<User> call() throws Exception {
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
+ public List<User> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
try {
- final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
- final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
- final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
- final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
+ int _argIndex = 1;
+ if (ids == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (int _item : ids) {
+ _stmt.bindLong(_argIndex, _item);
+ _argIndex++;
+ }
+ }
+ final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+ final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
final List<User> _result = new ArrayList<User>();
- while (_cursor.moveToNext()) {
+ while (_stmt.step()) {
final User _item_1;
_item_1 = new User();
- _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
- if (_cursor.isNull(_cursorIndexOfName)) {
+ _item_1.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+ if (_stmt.isNull(_cursorIndexOfName)) {
_item_1.name = null;
} else {
- _item_1.name = _cursor.getString(_cursorIndexOfName);
+ _item_1.name = _stmt.getText(_cursorIndexOfName);
}
final String _tmpLastName;
- if (_cursor.isNull(_cursorIndexOfLastName)) {
+ if (_stmt.isNull(_cursorIndexOfLastName)) {
_tmpLastName = null;
} else {
- _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+ _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
}
_item_1.setLastName(_tmpLastName);
- _item_1.age = _cursor.getInt(_cursorIndexOfAge);
+ _item_1.age = (int) (_stmt.getLong(_cursorIndexOfAge));
_result.add(_item_1);
}
return _result;
} finally {
- _cursor.close();
+ _stmt.close();
}
}
-
- @Override
- protected void finalize() {
- _statement.release();
- }
});
}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/DeletionDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/DeletionDao.java
index e287d17..bbbac5e 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/DeletionDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/DeletionDao.java
@@ -3,19 +3,17 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityDeletionOrUpdateAdapter;
import androidx.room.RoomDatabase;
+import androidx.room.RxRoom;
import androidx.room.util.DBUtil;
import androidx.room.util.SQLiteConnectionUtil;
import androidx.room.util.StringUtil;
import androidx.sqlite.SQLiteConnection;
import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteStatement;
import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Single;
import java.lang.Class;
-import java.lang.Exception;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
@@ -24,8 +22,8 @@
import java.lang.Void;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Callable;
import javax.annotation.processing.Generated;
+import kotlin.Unit;
import kotlin.jvm.functions.Function1;
@Generated("androidx.room.RoomProcessor")
@@ -35,8 +33,6 @@
private final EntityDeleteOrUpdateAdapter<User> __deleteAdapterOfUser;
- private final EntityDeletionOrUpdateAdapter<User> __deleteCompatAdapterOfUser;
-
private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
private final EntityDeleteOrUpdateAdapter<Book> __deleteAdapterOfBook;
@@ -55,18 +51,6 @@
statement.bindLong(1, entity.uid);
}
};
- this.__deleteCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
- @Override
- @NonNull
- protected String createQuery() {
- return "DELETE FROM `User` WHERE `uid` = ?";
- }
-
- @Override
- protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
- statement.bindLong(1, entity.uid);
- }
- };
this.__deleteAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
@Override
@NonNull
@@ -194,56 +178,38 @@
@Override
public Completable deleteUserCompletable(final User user) {
- return Completable.fromCallable(new Callable<Void>() {
+ return RxRoom.createCompletable(__db, false, true, new Function1<SQLiteConnection, Unit>() {
@Override
- @Nullable
- public Void call() throws Exception {
- __db.beginTransaction();
- try {
- __deleteCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return null;
- } finally {
- __db.endTransaction();
- }
+ @NonNull
+ public Unit invoke(@NonNull final SQLiteConnection _connection) {
+ __deleteAdapterOfUser.handle(_connection, user);
+ return Unit.INSTANCE;
}
});
}
@Override
public Single<Integer> deleteUserSingle(final User user) {
- return Single.fromCallable(new Callable<Integer>() {
+ return RxRoom.createSingle(__db, false, true, new Function1<SQLiteConnection, Integer>() {
@Override
@Nullable
- public Integer call() throws Exception {
- int _total = 0;
- __db.beginTransaction();
- try {
- _total += __deleteCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return _total;
- } finally {
- __db.endTransaction();
- }
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ int _result = 0;
+ _result += __deleteAdapterOfUser.handle(_connection, user);
+ return _result;
}
});
}
@Override
public Maybe<Integer> deleteUserMaybe(final User user) {
- return Maybe.fromCallable(new Callable<Integer>() {
+ return RxRoom.createMaybe(__db, false, true, new Function1<SQLiteConnection, Integer>() {
@Override
@Nullable
- public Integer call() throws Exception {
- int _total = 0;
- __db.beginTransaction();
- try {
- _total += __deleteCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return _total;
- } finally {
- __db.endTransaction();
- }
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ int _result = 0;
+ _result += __deleteAdapterOfUser.handle(_connection, user);
+ return _result;
}
});
}
@@ -296,21 +262,19 @@
@Override
public Completable deleteByUidCompletable(final int uid) {
- return Completable.fromCallable(new Callable<Void>() {
+ final String _sql = "DELETE FROM user where uid = ?";
+ return RxRoom.createCompletable(__db, false, true, new Function1<SQLiteConnection, Unit>() {
@Override
- @Nullable
- public Void call() throws Exception {
- final String _sql = "DELETE FROM user where uid = ?";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
- int _argIndex = 1;
- _stmt.bindLong(_argIndex, uid);
- __db.beginTransaction();
+ @NonNull
+ public Unit invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
try {
- _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return null;
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, uid);
+ _stmt.step();
+ return Unit.INSTANCE;
} finally {
- __db.endTransaction();
+ _stmt.close();
}
}
});
@@ -318,21 +282,19 @@
@Override
public Single<Integer> deleteByUidSingle(final int uid) {
- return Single.fromCallable(new Callable<Integer>() {
+ final String _sql = "DELETE FROM user where uid = ?";
+ return RxRoom.createSingle(__db, false, true, new Function1<SQLiteConnection, Integer>() {
@Override
@Nullable
- public Integer call() throws Exception {
- final String _sql = "DELETE FROM user where uid = ?";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
- int _argIndex = 1;
- _stmt.bindLong(_argIndex, uid);
- __db.beginTransaction();
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, uid);
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
} finally {
- __db.endTransaction();
+ _stmt.close();
}
}
});
@@ -340,21 +302,19 @@
@Override
public Maybe<Integer> deleteByUidMaybe(final int uid) {
- return Maybe.fromCallable(new Callable<Integer>() {
+ final String _sql = "DELETE FROM user where uid = ?";
+ return RxRoom.createMaybe(__db, false, true, new Function1<SQLiteConnection, Integer>() {
@Override
@Nullable
- public Integer call() throws Exception {
- final String _sql = "DELETE FROM user where uid = ?";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
- int _argIndex = 1;
- _stmt.bindLong(_argIndex, uid);
- __db.beginTransaction();
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, uid);
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
} finally {
- __db.endTransaction();
+ _stmt.close();
}
}
});
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/UpdateDao.java
index f3ca3df..8bbaf88 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/UpdateDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/UpdateDao.java
@@ -3,17 +3,16 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityDeletionOrUpdateAdapter;
import androidx.room.RoomDatabase;
+import androidx.room.RxRoom;
import androidx.room.util.DBUtil;
+import androidx.room.util.SQLiteConnectionUtil;
import androidx.sqlite.SQLiteConnection;
import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteStatement;
import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Single;
import java.lang.Class;
-import java.lang.Exception;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
@@ -21,8 +20,8 @@
import java.lang.Void;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Callable;
import javax.annotation.processing.Generated;
+import kotlin.Unit;
import kotlin.jvm.functions.Function1;
@Generated("androidx.room.RoomProcessor")
@@ -34,8 +33,6 @@
private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser_1;
- private final EntityDeletionOrUpdateAdapter<User> __updateCompatAdapterOfUser;
-
private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
private final EntityDeleteOrUpdateAdapter<Book> __updateAdapterOfBook;
@@ -90,30 +87,6 @@
statement.bindLong(5, entity.uid);
}
};
- this.__updateCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
- @Override
- @NonNull
- protected String createQuery() {
- return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
- }
-
- @Override
- protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
- statement.bindLong(1, entity.uid);
- if (entity.name == null) {
- statement.bindNull(2);
- } else {
- statement.bindString(2, entity.name);
- }
- if (entity.getLastName() == null) {
- statement.bindNull(3);
- } else {
- statement.bindString(3, entity.getLastName());
- }
- statement.bindLong(4, entity.age);
- statement.bindLong(5, entity.uid);
- }
- };
this.__updateAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
@Override
@NonNull
@@ -266,56 +239,38 @@
@Override
public Completable updateUserAndReturnCountCompletable(final User user) {
- return Completable.fromCallable(new Callable<Void>() {
+ return RxRoom.createCompletable(__db, false, true, new Function1<SQLiteConnection, Unit>() {
@Override
- @Nullable
- public Void call() throws Exception {
- __db.beginTransaction();
- try {
- __updateCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return null;
- } finally {
- __db.endTransaction();
- }
+ @NonNull
+ public Unit invoke(@NonNull final SQLiteConnection _connection) {
+ __updateAdapterOfUser.handle(_connection, user);
+ return Unit.INSTANCE;
}
});
}
@Override
public Single<Integer> updateUserAndReturnCountSingle(final User user) {
- return Single.fromCallable(new Callable<Integer>() {
+ return RxRoom.createSingle(__db, false, true, new Function1<SQLiteConnection, Integer>() {
@Override
@Nullable
- public Integer call() throws Exception {
- int _total = 0;
- __db.beginTransaction();
- try {
- _total += __updateCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return _total;
- } finally {
- __db.endTransaction();
- }
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ int _result = 0;
+ _result += __updateAdapterOfUser.handle(_connection, user);
+ return _result;
}
});
}
@Override
public Maybe<Integer> updateUserAndReturnCountMaybe(final User user) {
- return Maybe.fromCallable(new Callable<Integer>() {
+ return RxRoom.createMaybe(__db, false, true, new Function1<SQLiteConnection, Integer>() {
@Override
@Nullable
- public Integer call() throws Exception {
- int _total = 0;
- __db.beginTransaction();
- try {
- _total += __updateCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return _total;
- } finally {
- __db.endTransaction();
- }
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ int _result = 0;
+ _result += __updateAdapterOfUser.handle(_connection, user);
+ return _result;
}
});
}
@@ -390,19 +345,17 @@
@Override
public Completable ageUserAllCompletable() {
- return Completable.fromCallable(new Callable<Void>() {
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ return RxRoom.createCompletable(__db, false, true, new Function1<SQLiteConnection, Unit>() {
@Override
- @Nullable
- public Void call() throws Exception {
- final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
- __db.beginTransaction();
+ @NonNull
+ public Unit invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
try {
- _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return null;
+ _stmt.step();
+ return Unit.INSTANCE;
} finally {
- __db.endTransaction();
+ _stmt.close();
}
}
});
@@ -410,19 +363,17 @@
@Override
public Single<Integer> ageUserAllSingle() {
- return Single.fromCallable(new Callable<Integer>() {
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ return RxRoom.createSingle(__db, false, true, new Function1<SQLiteConnection, Integer>() {
@Override
@Nullable
- public Integer call() throws Exception {
- final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
- __db.beginTransaction();
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
} finally {
- __db.endTransaction();
+ _stmt.close();
}
}
});
@@ -430,19 +381,17 @@
@Override
public Maybe<Integer> ageUserAllMaybe() {
- return Maybe.fromCallable(new Callable<Integer>() {
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ return RxRoom.createMaybe(__db, false, true, new Function1<SQLiteConnection, Integer>() {
@Override
@Nullable
- public Integer call() throws Exception {
- final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
- __db.beginTransaction();
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
} finally {
- __db.endTransaction();
+ _stmt.close();
}
}
});
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
index a876756..54daec0 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
@@ -2,10 +2,8 @@
import android.database.Cursor;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.room.RoomDatabase;
-import androidx.room.RoomSQLiteQuery;
import androidx.room.guava.GuavaRoom;
import androidx.room.util.CursorUtil;
import androidx.room.util.DBUtil;
@@ -15,7 +13,6 @@
import androidx.sqlite.db.SupportSQLiteQuery;
import com.google.common.util.concurrent.ListenableFuture;
import java.lang.Class;
-import java.lang.Exception;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
@@ -24,7 +21,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Callable;
import javax.annotation.processing.Generated;
@Generated("androidx.room.RoomProcessor")
@@ -383,48 +379,38 @@
@Override
public LiveData<User> getByIdLive(final int id) {
final String _sql = "SELECT * FROM user where uid = ?";
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
- int _argIndex = 1;
- _statement.bindLong(_argIndex, id);
- return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<User>() {
- @Override
- @Nullable
- public User call() throws Exception {
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
- final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
- final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
- final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
- final User _result;
- if (_cursor.moveToFirst()) {
- _result = new User();
- _result.uid = _cursor.getInt(_cursorIndexOfUid);
- if (_cursor.isNull(_cursorIndexOfName)) {
- _result.name = null;
- } else {
- _result.name = _cursor.getString(_cursorIndexOfName);
- }
- final String _tmpLastName;
- if (_cursor.isNull(_cursorIndexOfLastName)) {
- _tmpLastName = null;
- } else {
- _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
- }
- _result.setLastName(_tmpLastName);
- _result.age = _cursor.getInt(_cursorIndexOfAge);
+ return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, id);
+ final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+ final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+ final User _result;
+ if (_stmt.step()) {
+ _result = new User();
+ _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+ if (_stmt.isNull(_cursorIndexOfName)) {
+ _result.name = null;
} else {
- _result = null;
+ _result.name = _stmt.getText(_cursorIndexOfName);
}
- return _result;
- } finally {
- _cursor.close();
+ final String _tmpLastName;
+ if (_stmt.isNull(_cursorIndexOfLastName)) {
+ _tmpLastName = null;
+ } else {
+ _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+ }
+ _result.setLastName(_tmpLastName);
+ _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+ } else {
+ _result = null;
}
- }
-
- @Override
- protected void finalize() {
- _statement.release();
+ return _result;
+ } finally {
+ _stmt.close();
}
});
}
@@ -437,56 +423,45 @@
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
_stringBuilder.append(")");
final String _sql = _stringBuilder.toString();
- final int _argCount = 0 + _inputSize;
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
- int _argIndex = 1;
- if (ids == null) {
- _statement.bindNull(_argIndex);
- } else {
- for (int _item : ids) {
- _statement.bindLong(_argIndex, _item);
- _argIndex++;
- }
- }
- return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<List<User>>() {
- @Override
- @Nullable
- public List<User> call() throws Exception {
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
- final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
- final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
- final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
- final List<User> _result = new ArrayList<User>();
- while (_cursor.moveToNext()) {
- final User _item_1;
- _item_1 = new User();
- _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
- if (_cursor.isNull(_cursorIndexOfName)) {
- _item_1.name = null;
- } else {
- _item_1.name = _cursor.getString(_cursorIndexOfName);
- }
- final String _tmpLastName;
- if (_cursor.isNull(_cursorIndexOfLastName)) {
- _tmpLastName = null;
- } else {
- _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
- }
- _item_1.setLastName(_tmpLastName);
- _item_1.age = _cursor.getInt(_cursorIndexOfAge);
- _result.add(_item_1);
+ return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ if (ids == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (int _item : ids) {
+ _stmt.bindLong(_argIndex, _item);
+ _argIndex++;
}
- return _result;
- } finally {
- _cursor.close();
}
- }
-
- @Override
- protected void finalize() {
- _statement.release();
+ final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+ final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+ final List<User> _result = new ArrayList<User>();
+ while (_stmt.step()) {
+ final User _item_1;
+ _item_1 = new User();
+ _item_1.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+ if (_stmt.isNull(_cursorIndexOfName)) {
+ _item_1.name = null;
+ } else {
+ _item_1.name = _stmt.getText(_cursorIndexOfName);
+ }
+ final String _tmpLastName;
+ if (_stmt.isNull(_cursorIndexOfLastName)) {
+ _tmpLastName = null;
+ } else {
+ _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+ }
+ _item_1.setLastName(_tmpLastName);
+ _item_1.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+ _result.add(_item_1);
+ }
+ return _result;
+ } finally {
+ _stmt.close();
}
});
}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
index 4d0654d..d81f936 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
@@ -1,30 +1,26 @@
package foo.bar;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityDeletionOrUpdateAdapter;
import androidx.room.RoomDatabase;
+import androidx.room.RxRoom;
import androidx.room.util.DBUtil;
import androidx.room.util.SQLiteConnectionUtil;
import androidx.room.util.StringUtil;
import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteStatement;
import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Single;
import java.lang.Class;
-import java.lang.Exception;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.lang.SuppressWarnings;
-import java.lang.Void;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Callable;
import javax.annotation.processing.Generated;
+import kotlin.Unit;
@Generated("androidx.room.RoomProcessor")
@SuppressWarnings({"unchecked", "deprecation", "removal"})
@@ -33,8 +29,6 @@
private final EntityDeleteOrUpdateAdapter<User> __deleteAdapterOfUser;
- private final EntityDeletionOrUpdateAdapter<User> __deleteCompatAdapterOfUser;
-
private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
private final EntityDeleteOrUpdateAdapter<Book> __deleteAdapterOfBook;
@@ -53,19 +47,6 @@
statement.bindLong(1, entity.uid);
}
};
- this.__deleteCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
- @Override
- @NonNull
- protected String createQuery() {
- return "DELETE FROM `User` WHERE `uid` = ?";
- }
-
- @Override
- protected void bind(@NonNull final SupportSQLiteStatement statement,
- @NonNull final User entity) {
- statement.bindLong(1, entity.uid);
- }
- };
this.__deleteAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
@Override
@NonNull
@@ -158,57 +139,27 @@
@Override
public Completable deleteUserCompletable(final User user) {
- return Completable.fromCallable(new Callable<Void>() {
- @Override
- @Nullable
- public Void call() throws Exception {
- __db.beginTransaction();
- try {
- __deleteCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return null;
- } finally {
- __db.endTransaction();
- }
- }
+ return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+ __deleteAdapterOfUser.handle(_connection, user);
+ return Unit.INSTANCE;
});
}
@Override
public Single<Integer> deleteUserSingle(final User user) {
- return Single.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- int _total = 0;
- __db.beginTransaction();
- try {
- _total += __deleteCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return _total;
- } finally {
- __db.endTransaction();
- }
- }
+ return RxRoom.createSingle(__db, false, true, (_connection) -> {
+ int _result = 0;
+ _result += __deleteAdapterOfUser.handle(_connection, user);
+ return _result;
});
}
@Override
public Maybe<Integer> deleteUserMaybe(final User user) {
- return Maybe.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- int _total = 0;
- __db.beginTransaction();
- try {
- _total += __deleteCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return _total;
- } finally {
- __db.endTransaction();
- }
- }
+ return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+ int _result = 0;
+ _result += __deleteAdapterOfUser.handle(_connection, user);
+ return _result;
});
}
@@ -248,66 +199,48 @@
@Override
public Completable deleteByUidCompletable(final int uid) {
- return Completable.fromCallable(new Callable<Void>() {
- @Override
- @Nullable
- public Void call() throws Exception {
- final String _sql = "DELETE FROM user where uid = ?";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+ final String _sql = "DELETE FROM user where uid = ?";
+ return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
int _argIndex = 1;
_stmt.bindLong(_argIndex, uid);
- __db.beginTransaction();
- try {
- _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return null;
- } finally {
- __db.endTransaction();
- }
+ _stmt.step();
+ return Unit.INSTANCE;
+ } finally {
+ _stmt.close();
}
});
}
@Override
public Single<Integer> deleteByUidSingle(final int uid) {
- return Single.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- final String _sql = "DELETE FROM user where uid = ?";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+ final String _sql = "DELETE FROM user where uid = ?";
+ return RxRoom.createSingle(__db, false, true, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
int _argIndex = 1;
_stmt.bindLong(_argIndex, uid);
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+ } finally {
+ _stmt.close();
}
});
}
@Override
public Maybe<Integer> deleteByUidMaybe(final int uid) {
- return Maybe.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- final String _sql = "DELETE FROM user where uid = ?";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+ final String _sql = "DELETE FROM user where uid = ?";
+ return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
int _argIndex = 1;
_stmt.bindLong(_argIndex, uid);
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+ } finally {
+ _stmt.close();
}
});
}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
index a94f1d5..41ba21c 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
@@ -1,27 +1,24 @@
package foo.bar;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityDeletionOrUpdateAdapter;
import androidx.room.RoomDatabase;
+import androidx.room.RxRoom;
import androidx.room.util.DBUtil;
+import androidx.room.util.SQLiteConnectionUtil;
import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteStatement;
import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Single;
import java.lang.Class;
-import java.lang.Exception;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
import java.lang.SuppressWarnings;
-import java.lang.Void;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Callable;
import javax.annotation.processing.Generated;
+import kotlin.Unit;
@Generated("androidx.room.RoomProcessor")
@SuppressWarnings({"unchecked", "deprecation", "removal"})
@@ -32,8 +29,6 @@
private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser_1;
- private final EntityDeletionOrUpdateAdapter<User> __updateCompatAdapterOfUser;
-
private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
private final EntityDeleteOrUpdateAdapter<Book> __updateAdapterOfBook;
@@ -88,31 +83,6 @@
statement.bindLong(5, entity.uid);
}
};
- this.__updateCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
- @Override
- @NonNull
- protected String createQuery() {
- return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
- }
-
- @Override
- protected void bind(@NonNull final SupportSQLiteStatement statement,
- @NonNull final User entity) {
- statement.bindLong(1, entity.uid);
- if (entity.name == null) {
- statement.bindNull(2);
- } else {
- statement.bindString(2, entity.name);
- }
- if (entity.getLastName() == null) {
- statement.bindNull(3);
- } else {
- statement.bindString(3, entity.getLastName());
- }
- statement.bindLong(4, entity.age);
- statement.bindLong(5, entity.uid);
- }
- };
this.__updateAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
@Override
@NonNull
@@ -218,57 +188,27 @@
@Override
public Completable updateUserAndReturnCountCompletable(final User user) {
- return Completable.fromCallable(new Callable<Void>() {
- @Override
- @Nullable
- public Void call() throws Exception {
- __db.beginTransaction();
- try {
- __updateCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return null;
- } finally {
- __db.endTransaction();
- }
- }
+ return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+ __updateAdapterOfUser.handle(_connection, user);
+ return Unit.INSTANCE;
});
}
@Override
public Single<Integer> updateUserAndReturnCountSingle(final User user) {
- return Single.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- int _total = 0;
- __db.beginTransaction();
- try {
- _total += __updateCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return _total;
- } finally {
- __db.endTransaction();
- }
- }
+ return RxRoom.createSingle(__db, false, true, (_connection) -> {
+ int _result = 0;
+ _result += __updateAdapterOfUser.handle(_connection, user);
+ return _result;
});
}
@Override
public Maybe<Integer> updateUserAndReturnCountMaybe(final User user) {
- return Maybe.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- int _total = 0;
- __db.beginTransaction();
- try {
- _total += __updateCompatAdapterOfUser.handle(user);
- __db.setTransactionSuccessful();
- return _total;
- } finally {
- __db.endTransaction();
- }
- }
+ return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+ int _result = 0;
+ _result += __updateAdapterOfUser.handle(_connection, user);
+ return _result;
});
}
@@ -326,60 +266,42 @@
@Override
public Completable ageUserAllCompletable() {
- return Completable.fromCallable(new Callable<Void>() {
- @Override
- @Nullable
- public Void call() throws Exception {
- final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
- __db.beginTransaction();
- try {
- _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return null;
- } finally {
- __db.endTransaction();
- }
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ _stmt.step();
+ return Unit.INSTANCE;
+ } finally {
+ _stmt.close();
}
});
}
@Override
public Single<Integer> ageUserAllSingle() {
- return Single.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ return RxRoom.createSingle(__db, false, true, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+ } finally {
+ _stmt.close();
}
});
}
@Override
public Maybe<Integer> ageUserAllMaybe() {
- return Maybe.fromCallable(new Callable<Integer>() {
- @Override
- @Nullable
- public Integer call() throws Exception {
- final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+ } finally {
+ _stmt.close();
}
});
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt
index 3ac3f8a..18e3a2b 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt
@@ -1,17 +1,15 @@
-import android.database.Cursor
-import androidx.room.EmptyResultSetException
import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
-import androidx.room.RoomSQLiteQuery.Companion.acquire
-import androidx.room.RxRoom
+import androidx.room.RxRoom.Companion.createFlowable
+import androidx.room.RxRoom.Companion.createMaybe
+import androidx.room.RxRoom.Companion.createObservable
+import androidx.room.RxRoom.Companion.createSingle
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.query
+import androidx.sqlite.SQLiteStatement
import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.Observable
import io.reactivex.Single
-import java.util.concurrent.Callable
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.String
@@ -37,43 +35,35 @@
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return RxRoom.createFlowable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity> {
- public override fun call(): MyEntity {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
+ return createFlowable(__db, false, arrayOf("MyEntity")) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String? in arg) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex)
} else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _stmt.bindText(_argIndex, _item)
}
- return _result
- } finally {
- _cursor.close()
+ _argIndex++
}
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpOther: String
+ _tmpOther = _stmt.getText(_cursorIndexOfOther)
+ _result = MyEntity(_tmpPk,_tmpOther)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
-
- protected fun finalize() {
- _statement.release()
- }
- })
+ }
}
public override fun getObservable(vararg arg: String?): Observable<MyEntity> {
@@ -83,43 +73,35 @@
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return RxRoom.createObservable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity> {
- public override fun call(): MyEntity {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
+ return createObservable(__db, false, arrayOf("MyEntity")) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String? in arg) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex)
} else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _stmt.bindText(_argIndex, _item)
}
- return _result
- } finally {
- _cursor.close()
+ _argIndex++
}
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpOther: String
+ _tmpOther = _stmt.getText(_cursorIndexOfOther)
+ _result = MyEntity(_tmpPk,_tmpOther)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
-
- protected fun finalize() {
- _statement.release()
- }
- })
+ }
}
public override fun getSingle(vararg arg: String?): Single<MyEntity> {
@@ -129,46 +111,35 @@
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return RxRoom.createSingle(object : Callable<MyEntity?> {
- public override fun call(): MyEntity? {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity?
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
+ return createSingle(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String? in arg) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex)
} else {
- _result = null
+ _stmt.bindText(_argIndex, _item)
}
- if (_result == null) {
- throw EmptyResultSetException("Query returned empty result set: " + _statement.sql)
- }
- return _result
- } finally {
- _cursor.close()
+ _argIndex++
}
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+ val _result: MyEntity?
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpOther: String
+ _tmpOther = _stmt.getText(_cursorIndexOfOther)
+ _result = MyEntity(_tmpPk,_tmpOther)
+ } else {
+ _result = null
+ }
+ _result
+ } finally {
+ _stmt.close()
}
-
- protected fun finalize() {
- _statement.release()
- }
- })
+ }
}
public override fun getMaybe(vararg arg: String?): Maybe<MyEntity> {
@@ -178,230 +149,35 @@
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return Maybe.fromCallable(object : Callable<MyEntity?> {
- public override fun call(): MyEntity? {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity?
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
+ return createMaybe(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String? in arg) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex)
} else {
- _result = null
+ _stmt.bindText(_argIndex, _item)
}
- return _result
- } finally {
- _cursor.close()
+ _argIndex++
}
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+ val _result: MyEntity?
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpOther: String
+ _tmpOther = _stmt.getText(_cursorIndexOfOther)
+ _result = MyEntity(_tmpPk,_tmpOther)
+ } else {
+ _result = null
+ }
+ _result
+ } finally {
+ _stmt.close()
}
-
- protected fun finalize() {
- _statement.release()
- }
- })
- }
-
- public override fun getFlowableNullable(vararg arg: String?): Flowable<MyEntity?> {
- val _stringBuilder: StringBuilder = StringBuilder()
- _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
- val _inputSize: Int = arg.size
- appendPlaceholders(_stringBuilder, _inputSize)
- _stringBuilder.append(")")
- val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
}
- return RxRoom.createFlowable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity?> {
- public override fun call(): MyEntity? {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity?
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
- } else {
- _result = null
- }
- return _result
- } finally {
- _cursor.close()
- }
- }
-
- protected fun finalize() {
- _statement.release()
- }
- })
- }
-
- public override fun getObservableNullable(vararg arg: String?): Observable<MyEntity?> {
- val _stringBuilder: StringBuilder = StringBuilder()
- _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
- val _inputSize: Int = arg.size
- appendPlaceholders(_stringBuilder, _inputSize)
- _stringBuilder.append(")")
- val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return RxRoom.createObservable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity?> {
- public override fun call(): MyEntity? {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity?
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
- } else {
- _result = null
- }
- return _result
- } finally {
- _cursor.close()
- }
- }
-
- protected fun finalize() {
- _statement.release()
- }
- })
- }
-
- public override fun getSingleNullable(vararg arg: String?): Single<MyEntity?> {
- val _stringBuilder: StringBuilder = StringBuilder()
- _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
- val _inputSize: Int = arg.size
- appendPlaceholders(_stringBuilder, _inputSize)
- _stringBuilder.append(")")
- val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return RxRoom.createSingle(object : Callable<MyEntity?> {
- public override fun call(): MyEntity? {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity?
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
- } else {
- _result = null
- }
- if (_result == null) {
- throw EmptyResultSetException("Query returned empty result set: " + _statement.sql)
- }
- return _result
- } finally {
- _cursor.close()
- }
- }
-
- protected fun finalize() {
- _statement.release()
- }
- })
- }
-
- public override fun getMaybeNullable(vararg arg: String?): Maybe<MyEntity?> {
- val _stringBuilder: StringBuilder = StringBuilder()
- _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
- val _inputSize: Int = arg.size
- appendPlaceholders(_stringBuilder, _inputSize)
- _stringBuilder.append(")")
- val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return Maybe.fromCallable(object : Callable<MyEntity?> {
- public override fun call(): MyEntity? {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity?
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
- } else {
- _result = null
- }
- return _result
- } finally {
- _cursor.close()
- }
- }
-
- protected fun finalize() {
- _statement.release()
- }
- })
}
public companion object {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt
index 491de60..463fa8b 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt
@@ -1,17 +1,15 @@
-import android.database.Cursor
import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
-import androidx.room.RoomSQLiteQuery.Companion.acquire
-import androidx.room.rxjava3.EmptyResultSetException
-import androidx.room.rxjava3.RxRoom
+import androidx.room.rxjava3.createFlowable
+import androidx.room.rxjava3.createMaybe
+import androidx.room.rxjava3.createObservable
+import androidx.room.rxjava3.createSingle
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.query
+import androidx.sqlite.SQLiteStatement
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
-import java.util.concurrent.Callable
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.String
@@ -37,43 +35,35 @@
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return RxRoom.createFlowable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity> {
- public override fun call(): MyEntity {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
+ return createFlowable(__db, false, arrayOf("MyEntity")) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String? in arg) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex)
} else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _stmt.bindText(_argIndex, _item)
}
- return _result
- } finally {
- _cursor.close()
+ _argIndex++
}
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpOther: String
+ _tmpOther = _stmt.getText(_cursorIndexOfOther)
+ _result = MyEntity(_tmpPk,_tmpOther)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
-
- protected fun finalize() {
- _statement.release()
- }
- })
+ }
}
public override fun getObservable(vararg arg: String?): Observable<MyEntity> {
@@ -83,43 +73,35 @@
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return RxRoom.createObservable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity> {
- public override fun call(): MyEntity {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
+ return createObservable(__db, false, arrayOf("MyEntity")) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String? in arg) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex)
} else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _stmt.bindText(_argIndex, _item)
}
- return _result
- } finally {
- _cursor.close()
+ _argIndex++
}
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpOther: String
+ _tmpOther = _stmt.getText(_cursorIndexOfOther)
+ _result = MyEntity(_tmpPk,_tmpOther)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
-
- protected fun finalize() {
- _statement.release()
- }
- })
+ }
}
public override fun getSingle(vararg arg: String?): Single<MyEntity> {
@@ -129,46 +111,35 @@
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return RxRoom.createSingle(object : Callable<MyEntity?> {
- public override fun call(): MyEntity? {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity?
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
+ return createSingle(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String? in arg) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex)
} else {
- _result = null
+ _stmt.bindText(_argIndex, _item)
}
- if (_result == null) {
- throw EmptyResultSetException("Query returned empty result set: " + _statement.sql)
- }
- return _result
- } finally {
- _cursor.close()
+ _argIndex++
}
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+ val _result: MyEntity?
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpOther: String
+ _tmpOther = _stmt.getText(_cursorIndexOfOther)
+ _result = MyEntity(_tmpPk,_tmpOther)
+ } else {
+ _result = null
+ }
+ _result
+ } finally {
+ _stmt.close()
}
-
- protected fun finalize() {
- _statement.release()
- }
- })
+ }
}
public override fun getMaybe(vararg arg: String?): Maybe<MyEntity> {
@@ -178,230 +149,35 @@
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return Maybe.fromCallable(object : Callable<MyEntity?> {
- public override fun call(): MyEntity? {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity?
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
+ return createMaybe(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String? in arg) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex)
} else {
- _result = null
+ _stmt.bindText(_argIndex, _item)
}
- return _result
- } finally {
- _cursor.close()
+ _argIndex++
}
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+ val _result: MyEntity?
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpOther: String
+ _tmpOther = _stmt.getText(_cursorIndexOfOther)
+ _result = MyEntity(_tmpPk,_tmpOther)
+ } else {
+ _result = null
+ }
+ _result
+ } finally {
+ _stmt.close()
}
-
- protected fun finalize() {
- _statement.release()
- }
- })
- }
-
- public override fun getFlowableNullable(vararg arg: String?): Flowable<MyEntity?> {
- val _stringBuilder: StringBuilder = StringBuilder()
- _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
- val _inputSize: Int = arg.size
- appendPlaceholders(_stringBuilder, _inputSize)
- _stringBuilder.append(")")
- val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
}
- return RxRoom.createFlowable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity?> {
- public override fun call(): MyEntity? {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity?
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
- } else {
- _result = null
- }
- return _result
- } finally {
- _cursor.close()
- }
- }
-
- protected fun finalize() {
- _statement.release()
- }
- })
- }
-
- public override fun getObservableNullable(vararg arg: String?): Observable<MyEntity?> {
- val _stringBuilder: StringBuilder = StringBuilder()
- _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
- val _inputSize: Int = arg.size
- appendPlaceholders(_stringBuilder, _inputSize)
- _stringBuilder.append(")")
- val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return RxRoom.createObservable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity?> {
- public override fun call(): MyEntity? {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity?
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
- } else {
- _result = null
- }
- return _result
- } finally {
- _cursor.close()
- }
- }
-
- protected fun finalize() {
- _statement.release()
- }
- })
- }
-
- public override fun getSingleNullable(vararg arg: String?): Single<MyEntity?> {
- val _stringBuilder: StringBuilder = StringBuilder()
- _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
- val _inputSize: Int = arg.size
- appendPlaceholders(_stringBuilder, _inputSize)
- _stringBuilder.append(")")
- val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return RxRoom.createSingle(object : Callable<MyEntity?> {
- public override fun call(): MyEntity? {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity?
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
- } else {
- _result = null
- }
- if (_result == null) {
- throw EmptyResultSetException("Query returned empty result set: " + _statement.sql)
- }
- return _result
- } finally {
- _cursor.close()
- }
- }
-
- protected fun finalize() {
- _statement.release()
- }
- })
- }
-
- public override fun getMaybeNullable(vararg arg: String?): Maybe<MyEntity?> {
- val _stringBuilder: StringBuilder = StringBuilder()
- _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
- val _inputSize: Int = arg.size
- appendPlaceholders(_stringBuilder, _inputSize)
- _stringBuilder.append(")")
- val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return Maybe.fromCallable(object : Callable<MyEntity?> {
- public override fun call(): MyEntity? {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity?
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
- } else {
- _result = null
- }
- return _result
- } finally {
- _cursor.close()
- }
- }
-
- protected fun finalize() {
- _statement.release()
- }
- })
}
public companion object {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt
index 8a831c0..be0e2f6 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt
@@ -1,12 +1,8 @@
-import android.database.Cursor
import androidx.lifecycle.LiveData
import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
-import androidx.room.RoomSQLiteQuery.Companion.acquire
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.query
-import java.util.concurrent.Callable
+import androidx.sqlite.SQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.String
@@ -32,44 +28,35 @@
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return __db.invalidationTracker.createLiveData(arrayOf("MyEntity"), false, object :
- Callable<MyEntity?> {
- public override fun call(): MyEntity? {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity?
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
+ return __db.invalidationTracker.createLiveData(arrayOf("MyEntity"), false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String? in arg) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex)
} else {
- _result = null
+ _stmt.bindText(_argIndex, _item)
}
- return _result
- } finally {
- _cursor.close()
+ _argIndex++
}
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+ val _result: MyEntity?
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpOther: String
+ _tmpOther = _stmt.getText(_cursorIndexOfOther)
+ _result = MyEntity(_tmpPk,_tmpOther)
+ } else {
+ _result = null
+ }
+ _result
+ } finally {
+ _stmt.close()
}
-
- protected fun finalize() {
- _statement.release()
- }
- })
+ }
}
public override fun getLiveDataNullable(vararg arg: String?): LiveData<MyEntity?> {
@@ -79,44 +66,35 @@
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- return __db.invalidationTracker.createLiveData(arrayOf("MyEntity"), false, object :
- Callable<MyEntity?> {
- public override fun call(): MyEntity? {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MyEntity?
- if (_cursor.moveToFirst()) {
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _result = MyEntity(_tmpPk,_tmpOther)
+ return __db.invalidationTracker.createLiveData(arrayOf("MyEntity"), false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String? in arg) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex)
} else {
- _result = null
+ _stmt.bindText(_argIndex, _item)
}
- return _result
- } finally {
- _cursor.close()
+ _argIndex++
}
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+ val _result: MyEntity?
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpOther: String
+ _tmpOther = _stmt.getText(_cursorIndexOfOther)
+ _result = MyEntity(_tmpPk,_tmpOther)
+ } else {
+ _result = null
+ }
+ _result
+ } finally {
+ _stmt.close()
}
-
- protected fun finalize() {
- _statement.release()
- }
- })
+ }
}
public companion object {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx2.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx2.kt
index e3f48cc..fbc5b8f2 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx2.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx2.kt
@@ -1,10 +1,12 @@
import androidx.room.RoomDatabase
-import androidx.sqlite.db.SupportSQLiteStatement
+import androidx.room.RxRoom.Companion.createCompletable
+import androidx.room.RxRoom.Companion.createMaybe
+import androidx.room.RxRoom.Companion.createSingle
+import androidx.room.util.getLastInsertedRowId
+import androidx.sqlite.SQLiteStatement
import io.reactivex.Completable
import io.reactivex.Maybe
import io.reactivex.Single
-import java.lang.Void
-import java.util.concurrent.Callable
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.Long
@@ -23,65 +25,55 @@
this.__db = __db
}
- public override fun insertPublisherSingle(id: String, name: String): Single<Long> =
- Single.fromCallable(object : Callable<Long?> {
- public override fun call(): Long? {
- val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
- val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
- var _argIndex: Int = 1
- _stmt.bindString(_argIndex, id)
- _argIndex = 2
- _stmt.bindString(_argIndex, name)
- __db.beginTransaction()
+ public override fun insertPublisherSingle(id: String, name: String): Single<Long> {
+ val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+ return createSingle(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
try {
- val _result: Long? = _stmt.executeInsert()
- __db.setTransactionSuccessful()
- return _result
+ var _argIndex: Int = 1
+ _stmt.bindText(_argIndex, id)
+ _argIndex = 2
+ _stmt.bindText(_argIndex, name)
+ _stmt.step()
+ getLastInsertedRowId(_connection)
} finally {
- __db.endTransaction()
+ _stmt.close()
}
}
- })
+ }
- public override fun insertPublisherMaybe(id: String, name: String): Maybe<Long> =
- Maybe.fromCallable(object : Callable<Long?> {
- public override fun call(): Long? {
- val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
- val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
- var _argIndex: Int = 1
- _stmt.bindString(_argIndex, id)
- _argIndex = 2
- _stmt.bindString(_argIndex, name)
- __db.beginTransaction()
+ public override fun insertPublisherMaybe(id: String, name: String): Maybe<Long> {
+ val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+ return createMaybe(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
try {
- val _result: Long? = _stmt.executeInsert()
- __db.setTransactionSuccessful()
- return _result
+ var _argIndex: Int = 1
+ _stmt.bindText(_argIndex, id)
+ _argIndex = 2
+ _stmt.bindText(_argIndex, name)
+ _stmt.step()
+ getLastInsertedRowId(_connection)
} finally {
- __db.endTransaction()
+ _stmt.close()
}
}
- })
+ }
- public override fun insertPublisherCompletable(id: String, name: String): Completable =
- Completable.fromCallable(object : Callable<Void?> {
- public override fun call(): Void? {
- val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
- val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
- var _argIndex: Int = 1
- _stmt.bindString(_argIndex, id)
- _argIndex = 2
- _stmt.bindString(_argIndex, name)
- __db.beginTransaction()
+ public override fun insertPublisherCompletable(id: String, name: String): Completable {
+ val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+ return createCompletable(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
try {
- _stmt.executeInsert()
- __db.setTransactionSuccessful()
- return null
+ var _argIndex: Int = 1
+ _stmt.bindText(_argIndex, id)
+ _argIndex = 2
+ _stmt.bindText(_argIndex, name)
+ _stmt.step()
} finally {
- __db.endTransaction()
+ _stmt.close()
}
}
- })
+ }
public companion object {
public fun getRequiredConverters(): List<KClass<*>> = emptyList()
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx3.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx3.kt
index 85d36b7..91a3312 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx3.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx3.kt
@@ -1,10 +1,12 @@
import androidx.room.RoomDatabase
-import androidx.sqlite.db.SupportSQLiteStatement
+import androidx.room.rxjava3.createCompletable
+import androidx.room.rxjava3.createMaybe
+import androidx.room.rxjava3.createSingle
+import androidx.room.util.getLastInsertedRowId
+import androidx.sqlite.SQLiteStatement
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.core.Single
-import java.lang.Void
-import java.util.concurrent.Callable
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.Long
@@ -23,65 +25,55 @@
this.__db = __db
}
- public override fun insertPublisherSingle(id: String, name: String): Single<Long> =
- Single.fromCallable(object : Callable<Long?> {
- public override fun call(): Long? {
- val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
- val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
- var _argIndex: Int = 1
- _stmt.bindString(_argIndex, id)
- _argIndex = 2
- _stmt.bindString(_argIndex, name)
- __db.beginTransaction()
+ public override fun insertPublisherSingle(id: String, name: String): Single<Long> {
+ val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+ return createSingle(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
try {
- val _result: Long? = _stmt.executeInsert()
- __db.setTransactionSuccessful()
- return _result
+ var _argIndex: Int = 1
+ _stmt.bindText(_argIndex, id)
+ _argIndex = 2
+ _stmt.bindText(_argIndex, name)
+ _stmt.step()
+ getLastInsertedRowId(_connection)
} finally {
- __db.endTransaction()
+ _stmt.close()
}
}
- })
+ }
- public override fun insertPublisherMaybe(id: String, name: String): Maybe<Long> =
- Maybe.fromCallable(object : Callable<Long?> {
- public override fun call(): Long? {
- val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
- val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
- var _argIndex: Int = 1
- _stmt.bindString(_argIndex, id)
- _argIndex = 2
- _stmt.bindString(_argIndex, name)
- __db.beginTransaction()
+ public override fun insertPublisherMaybe(id: String, name: String): Maybe<Long> {
+ val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+ return createMaybe(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
try {
- val _result: Long? = _stmt.executeInsert()
- __db.setTransactionSuccessful()
- return _result
+ var _argIndex: Int = 1
+ _stmt.bindText(_argIndex, id)
+ _argIndex = 2
+ _stmt.bindText(_argIndex, name)
+ _stmt.step()
+ getLastInsertedRowId(_connection)
} finally {
- __db.endTransaction()
+ _stmt.close()
}
}
- })
+ }
- public override fun insertPublisherCompletable(id: String, name: String): Completable =
- Completable.fromCallable(object : Callable<Void?> {
- public override fun call(): Void? {
- val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
- val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
- var _argIndex: Int = 1
- _stmt.bindString(_argIndex, id)
- _argIndex = 2
- _stmt.bindString(_argIndex, name)
- __db.beginTransaction()
+ public override fun insertPublisherCompletable(id: String, name: String): Completable {
+ val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+ return createCompletable(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
try {
- _stmt.executeInsert()
- __db.setTransactionSuccessful()
- return null
+ var _argIndex: Int = 1
+ _stmt.bindText(_argIndex, id)
+ _argIndex = 2
+ _stmt.bindText(_argIndex, name)
+ _stmt.step()
} finally {
- __db.endTransaction()
+ _stmt.close()
}
}
- })
+ }
public companion object {
public fun getRequiredConverters(): List<KClass<*>> = emptyList()
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
index 57b5fce..34749e2 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
@@ -1,12 +1,12 @@
-import androidx.room.EntityDeletionOrUpdateAdapter
-import androidx.room.EntityInsertionAdapter
-import androidx.room.EntityUpsertionAdapter
+import androidx.room.EntityDeleteOrUpdateAdapter
+import androidx.room.EntityInsertAdapter
+import androidx.room.EntityUpsertAdapter
import androidx.room.RoomDatabase
-import androidx.sqlite.db.SupportSQLiteStatement
+import androidx.room.RxRoom.Companion.createCompletable
+import androidx.room.RxRoom.Companion.createSingle
+import androidx.sqlite.SQLiteStatement
import io.reactivex.Completable
import io.reactivex.Single
-import java.lang.Void
-import java.util.concurrent.Callable
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.Long
@@ -22,175 +22,109 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
- private val __deleteCompatAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+ private val __deleteAdapterOfMyEntity: EntityDeleteOrUpdateAdapter<MyEntity>
- private val __updateCompatAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+ private val __updateAdapterOfMyEntity: EntityDeleteOrUpdateAdapter<MyEntity>
- private val __upsertionAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
+ private val __upsertAdapterOfMyEntity: EntityUpsertAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
- protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+ protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
- statement.bindString(2, entity.other)
+ statement.bindText(2, entity.other)
}
}
- this.__deleteCompatAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+ this.__deleteAdapterOfMyEntity = object : EntityDeleteOrUpdateAdapter<MyEntity>() {
protected override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
- protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+ protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
}
}
- this.__updateCompatAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+ this.__updateAdapterOfMyEntity = object : EntityDeleteOrUpdateAdapter<MyEntity>() {
protected override fun createQuery(): String =
"UPDATE OR ABORT `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
- protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+ protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
- statement.bindString(2, entity.other)
+ statement.bindText(2, entity.other)
statement.bindLong(3, entity.pk.toLong())
}
}
- this.__upsertionAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
- EntityInsertionAdapter<MyEntity>(__db) {
+ this.__upsertAdapterOfMyEntity = EntityUpsertAdapter<MyEntity>(object :
+ EntityInsertAdapter<MyEntity>() {
protected override fun createQuery(): String =
"INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
- protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+ protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
- statement.bindString(2, entity.other)
+ statement.bindText(2, entity.other)
}
- }, object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+ }, object : EntityDeleteOrUpdateAdapter<MyEntity>() {
protected override fun createQuery(): String =
"UPDATE `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
- protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+ protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
- statement.bindString(2, entity.other)
+ statement.bindText(2, entity.other)
statement.bindLong(3, entity.pk.toLong())
}
})
}
public override fun insertSingle(vararg entities: MyEntity): Single<List<Long>> =
- Single.fromCallable(object : Callable<List<Long>?> {
- public override fun call(): List<Long>? {
- __db.beginTransaction()
- try {
- val _result: List<Long>? = __insertionAdapterOfMyEntity.insertAndReturnIdsList(entities)
- __db.setTransactionSuccessful()
- return _result
- } finally {
- __db.endTransaction()
- }
- }
- })
+ createSingle(__db, false, true) { _connection ->
+ val _result: List<Long>? = __insertAdapterOfMyEntity.insertAndReturnIdsList(_connection,
+ entities)
+ _result
+ }
public override fun insertCompletable(vararg entities: MyEntity): Completable =
- Completable.fromCallable(object : Callable<Void?> {
- public override fun call(): Void? {
- __db.beginTransaction()
- try {
- __insertionAdapterOfMyEntity.insert(entities)
- __db.setTransactionSuccessful()
- return null
- } finally {
- __db.endTransaction()
- }
- }
- })
+ createCompletable(__db, false, true) { _connection ->
+ __insertAdapterOfMyEntity.insert(_connection, entities)
+ }
- public override fun deleteSingle(entity: MyEntity): Single<Int> = Single.fromCallable(object :
- Callable<Int?> {
- public override fun call(): Int? {
- var _total: Int = 0
- __db.beginTransaction()
- try {
- _total += __deleteCompatAdapterOfMyEntity.handle(entity)
- __db.setTransactionSuccessful()
- return _total
- } finally {
- __db.endTransaction()
- }
- }
- })
+ public override fun deleteSingle(entity: MyEntity): Single<Int> = createSingle(__db, false, true)
+ { _connection ->
+ var _result: Int = 0
+ _result += __deleteAdapterOfMyEntity.handle(_connection, entity)
+ _result
+ }
- public override fun deleteCompletable(entity: MyEntity): Completable =
- Completable.fromCallable(object : Callable<Void?> {
- public override fun call(): Void? {
- __db.beginTransaction()
- try {
- __deleteCompatAdapterOfMyEntity.handle(entity)
- __db.setTransactionSuccessful()
- return null
- } finally {
- __db.endTransaction()
- }
- }
- })
+ public override fun deleteCompletable(entity: MyEntity): Completable = createCompletable(__db,
+ false, true) { _connection ->
+ __deleteAdapterOfMyEntity.handle(_connection, entity)
+ }
- public override fun updateSingle(entity: MyEntity): Single<Int> = Single.fromCallable(object :
- Callable<Int?> {
- public override fun call(): Int? {
- var _total: Int = 0
- __db.beginTransaction()
- try {
- _total += __updateCompatAdapterOfMyEntity.handle(entity)
- __db.setTransactionSuccessful()
- return _total
- } finally {
- __db.endTransaction()
- }
- }
- })
+ public override fun updateSingle(entity: MyEntity): Single<Int> = createSingle(__db, false, true)
+ { _connection ->
+ var _result: Int = 0
+ _result += __updateAdapterOfMyEntity.handle(_connection, entity)
+ _result
+ }
- public override fun updateCompletable(entity: MyEntity): Completable =
- Completable.fromCallable(object : Callable<Void?> {
- public override fun call(): Void? {
- __db.beginTransaction()
- try {
- __updateCompatAdapterOfMyEntity.handle(entity)
- __db.setTransactionSuccessful()
- return null
- } finally {
- __db.endTransaction()
- }
- }
- })
+ public override fun updateCompletable(entity: MyEntity): Completable = createCompletable(__db,
+ false, true) { _connection ->
+ __updateAdapterOfMyEntity.handle(_connection, entity)
+ }
public override fun upsertSingle(vararg entities: MyEntity): Single<List<Long>> =
- Single.fromCallable(object : Callable<List<Long>?> {
- public override fun call(): List<Long>? {
- __db.beginTransaction()
- try {
- val _result: List<Long>? = __upsertionAdapterOfMyEntity.upsertAndReturnIdsList(entities)
- __db.setTransactionSuccessful()
- return _result
- } finally {
- __db.endTransaction()
- }
- }
- })
+ createSingle(__db, false, true) { _connection ->
+ val _result: List<Long>? = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(_connection,
+ entities)
+ _result
+ }
public override fun upsertCompletable(vararg entities: MyEntity): Completable =
- Completable.fromCallable(object : Callable<Void?> {
- public override fun call(): Void? {
- __db.beginTransaction()
- try {
- __upsertionAdapterOfMyEntity.upsert(entities)
- __db.setTransactionSuccessful()
- return null
- } finally {
- __db.endTransaction()
- }
- }
- })
+ createCompletable(__db, false, true) { _connection ->
+ __upsertAdapterOfMyEntity.upsert(_connection, entities)
+ }
public companion object {
public fun getRequiredConverters(): List<KClass<*>> = emptyList()
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
index e64428f..6867046 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
@@ -1,12 +1,12 @@
-import androidx.room.EntityDeletionOrUpdateAdapter
-import androidx.room.EntityInsertionAdapter
-import androidx.room.EntityUpsertionAdapter
+import androidx.room.EntityDeleteOrUpdateAdapter
+import androidx.room.EntityInsertAdapter
+import androidx.room.EntityUpsertAdapter
import androidx.room.RoomDatabase
-import androidx.sqlite.db.SupportSQLiteStatement
+import androidx.room.rxjava3.createCompletable
+import androidx.room.rxjava3.createSingle
+import androidx.sqlite.SQLiteStatement
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Single
-import java.lang.Void
-import java.util.concurrent.Callable
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.Long
@@ -22,175 +22,109 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
- private val __deleteCompatAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+ private val __deleteAdapterOfMyEntity: EntityDeleteOrUpdateAdapter<MyEntity>
- private val __updateCompatAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+ private val __updateAdapterOfMyEntity: EntityDeleteOrUpdateAdapter<MyEntity>
- private val __upsertionAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
+ private val __upsertAdapterOfMyEntity: EntityUpsertAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
- protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+ protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
- statement.bindString(2, entity.other)
+ statement.bindText(2, entity.other)
}
}
- this.__deleteCompatAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+ this.__deleteAdapterOfMyEntity = object : EntityDeleteOrUpdateAdapter<MyEntity>() {
protected override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
- protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+ protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
}
}
- this.__updateCompatAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+ this.__updateAdapterOfMyEntity = object : EntityDeleteOrUpdateAdapter<MyEntity>() {
protected override fun createQuery(): String =
"UPDATE OR ABORT `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
- protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+ protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
- statement.bindString(2, entity.other)
+ statement.bindText(2, entity.other)
statement.bindLong(3, entity.pk.toLong())
}
}
- this.__upsertionAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
- EntityInsertionAdapter<MyEntity>(__db) {
+ this.__upsertAdapterOfMyEntity = EntityUpsertAdapter<MyEntity>(object :
+ EntityInsertAdapter<MyEntity>() {
protected override fun createQuery(): String =
"INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
- protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+ protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
- statement.bindString(2, entity.other)
+ statement.bindText(2, entity.other)
}
- }, object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+ }, object : EntityDeleteOrUpdateAdapter<MyEntity>() {
protected override fun createQuery(): String =
"UPDATE `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
- protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+ protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
statement.bindLong(1, entity.pk.toLong())
- statement.bindString(2, entity.other)
+ statement.bindText(2, entity.other)
statement.bindLong(3, entity.pk.toLong())
}
})
}
public override fun insertSingle(vararg entities: MyEntity): Single<List<Long>> =
- Single.fromCallable(object : Callable<List<Long>?> {
- public override fun call(): List<Long>? {
- __db.beginTransaction()
- try {
- val _result: List<Long>? = __insertionAdapterOfMyEntity.insertAndReturnIdsList(entities)
- __db.setTransactionSuccessful()
- return _result
- } finally {
- __db.endTransaction()
- }
- }
- })
+ createSingle(__db, false, true) { _connection ->
+ val _result: List<Long>? = __insertAdapterOfMyEntity.insertAndReturnIdsList(_connection,
+ entities)
+ _result
+ }
public override fun insertCompletable(vararg entities: MyEntity): Completable =
- Completable.fromCallable(object : Callable<Void?> {
- public override fun call(): Void? {
- __db.beginTransaction()
- try {
- __insertionAdapterOfMyEntity.insert(entities)
- __db.setTransactionSuccessful()
- return null
- } finally {
- __db.endTransaction()
- }
- }
- })
+ createCompletable(__db, false, true) { _connection ->
+ __insertAdapterOfMyEntity.insert(_connection, entities)
+ }
- public override fun deleteSingle(entity: MyEntity): Single<Int> = Single.fromCallable(object :
- Callable<Int?> {
- public override fun call(): Int? {
- var _total: Int = 0
- __db.beginTransaction()
- try {
- _total += __deleteCompatAdapterOfMyEntity.handle(entity)
- __db.setTransactionSuccessful()
- return _total
- } finally {
- __db.endTransaction()
- }
- }
- })
+ public override fun deleteSingle(entity: MyEntity): Single<Int> = createSingle(__db, false, true)
+ { _connection ->
+ var _result: Int = 0
+ _result += __deleteAdapterOfMyEntity.handle(_connection, entity)
+ _result
+ }
- public override fun deleteCompletable(entity: MyEntity): Completable =
- Completable.fromCallable(object : Callable<Void?> {
- public override fun call(): Void? {
- __db.beginTransaction()
- try {
- __deleteCompatAdapterOfMyEntity.handle(entity)
- __db.setTransactionSuccessful()
- return null
- } finally {
- __db.endTransaction()
- }
- }
- })
+ public override fun deleteCompletable(entity: MyEntity): Completable = createCompletable(__db,
+ false, true) { _connection ->
+ __deleteAdapterOfMyEntity.handle(_connection, entity)
+ }
- public override fun updateSingle(entity: MyEntity): Single<Int> = Single.fromCallable(object :
- Callable<Int?> {
- public override fun call(): Int? {
- var _total: Int = 0
- __db.beginTransaction()
- try {
- _total += __updateCompatAdapterOfMyEntity.handle(entity)
- __db.setTransactionSuccessful()
- return _total
- } finally {
- __db.endTransaction()
- }
- }
- })
+ public override fun updateSingle(entity: MyEntity): Single<Int> = createSingle(__db, false, true)
+ { _connection ->
+ var _result: Int = 0
+ _result += __updateAdapterOfMyEntity.handle(_connection, entity)
+ _result
+ }
- public override fun updateCompletable(entity: MyEntity): Completable =
- Completable.fromCallable(object : Callable<Void?> {
- public override fun call(): Void? {
- __db.beginTransaction()
- try {
- __updateCompatAdapterOfMyEntity.handle(entity)
- __db.setTransactionSuccessful()
- return null
- } finally {
- __db.endTransaction()
- }
- }
- })
+ public override fun updateCompletable(entity: MyEntity): Completable = createCompletable(__db,
+ false, true) { _connection ->
+ __updateAdapterOfMyEntity.handle(_connection, entity)
+ }
public override fun upsertSingle(vararg entities: MyEntity): Single<List<Long>> =
- Single.fromCallable(object : Callable<List<Long>?> {
- public override fun call(): List<Long>? {
- __db.beginTransaction()
- try {
- val _result: List<Long>? = __upsertionAdapterOfMyEntity.upsertAndReturnIdsList(entities)
- __db.setTransactionSuccessful()
- return _result
- } finally {
- __db.endTransaction()
- }
- }
- })
+ createSingle(__db, false, true) { _connection ->
+ val _result: List<Long>? = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(_connection,
+ entities)
+ _result
+ }
public override fun upsertCompletable(vararg entities: MyEntity): Completable =
- Completable.fromCallable(object : Callable<Void?> {
- public override fun call(): Void? {
- __db.beginTransaction()
- try {
- __upsertionAdapterOfMyEntity.upsert(entities)
- __db.setTransactionSuccessful()
- return null
- } finally {
- __db.endTransaction()
- }
- }
- })
+ createCompletable(__db, false, true) { _connection ->
+ __upsertAdapterOfMyEntity.upsert(_connection, entities)
+ }
public companion object {
public fun getRequiredConverters(): List<KClass<*>> = emptyList()
diff --git a/room/room-gradle-plugin/src/test/test-data/multiplatform-project/src/jvmMain/kotlin/room/testapp/MyDatabase.kt b/room/room-gradle-plugin/src/test/test-data/multiplatform-project/src/jvmMain/kotlin/room/testapp/MyDatabase.kt
index 3690e65..c31091d 100644
--- a/room/room-gradle-plugin/src/test/test-data/multiplatform-project/src/jvmMain/kotlin/room/testapp/MyDatabase.kt
+++ b/room/room-gradle-plugin/src/test/test-data/multiplatform-project/src/jvmMain/kotlin/room/testapp/MyDatabase.kt
@@ -19,13 +19,10 @@
import androidx.room.*
@Database(entities = [JvmEntity::class], version = 1)
-@ConstructedBy(MyDatabaseCtor::class)
abstract class MyDatabase : RoomDatabase() {
abstract fun getMyDao(): MyDao
}
-expect object MyDatabaseCtor : RoomDatabaseConstructor<MyDatabase>
-
@Entity
data class JvmEntity(
@PrimaryKey val id: Long
diff --git a/room/room-paging-rxjava2/src/main/java/androidx/room/paging/rxjava2/LimitOffsetRxPagingSource.kt b/room/room-paging-rxjava2/src/main/java/androidx/room/paging/rxjava2/LimitOffsetRxPagingSource.kt
index db0f5ae..7bd8f30 100644
--- a/room/room-paging-rxjava2/src/main/java/androidx/room/paging/rxjava2/LimitOffsetRxPagingSource.kt
+++ b/room/room-paging-rxjava2/src/main/java/androidx/room/paging/rxjava2/LimitOffsetRxPagingSource.kt
@@ -24,7 +24,7 @@
import androidx.paging.rxjava2.RxPagingSource
import androidx.room.RoomDatabase
import androidx.room.RoomSQLiteQuery
-import androidx.room.RxRoom.createSingle
+import androidx.room.RxRoom
import androidx.room.paging.util.INITIAL_ITEM_COUNT
import androidx.room.paging.util.INVALID
import androidx.room.paging.util.ThreadSafeInvalidationObserver
@@ -56,7 +56,7 @@
override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, Value>> {
val scheduler = Schedulers.from(db.queryExecutor)
- return createSingle {
+ return RxRoom.createSingle {
observer.registerIfNecessary(db)
val tempCount = itemCount.get()
if (tempCount == INITIAL_ITEM_COUNT) {
diff --git a/room/room-paging-rxjava3/src/main/java/androidx/room/paging/rxjava3/LimitOffsetRxPagingSource.kt b/room/room-paging-rxjava3/src/main/java/androidx/room/paging/rxjava3/LimitOffsetRxPagingSource.kt
index 6067c44..a2792a0 100644
--- a/room/room-paging-rxjava3/src/main/java/androidx/room/paging/rxjava3/LimitOffsetRxPagingSource.kt
+++ b/room/room-paging-rxjava3/src/main/java/androidx/room/paging/rxjava3/LimitOffsetRxPagingSource.kt
@@ -30,7 +30,7 @@
import androidx.room.paging.util.getClippedRefreshKey
import androidx.room.paging.util.queryDatabase
import androidx.room.paging.util.queryItemCount
-import androidx.room.rxjava3.RxRoom.createSingle
+import androidx.room.rxjava3.createSingle
import androidx.sqlite.db.SupportSQLiteQuery
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
diff --git a/room/room-runtime/api/restricted_current.txt b/room/room-runtime/api/restricted_current.txt
index 5a6a8e5..2799d77 100644
--- a/room/room-runtime/api/restricted_current.txt
+++ b/room/room-runtime/api/restricted_current.txt
@@ -140,6 +140,7 @@
method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer observer);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @WorkerThread public void addWeakObserver(androidx.room.InvalidationTracker.Observer observer);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T> createLiveData(String[] tableNames, boolean inTransaction, java.util.concurrent.Callable<T?> computeFunction);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final <T> androidx.lifecycle.LiveData<T> createLiveData(String[] tableNames, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> computeFunction);
method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T> createLiveData(String[] tableNames, java.util.concurrent.Callable<T?> computeFunction);
method public final void refreshAsync();
method public void refreshVersionsAsync();
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationLiveDataContainer.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationLiveDataContainer.android.kt
index b9f5b2a..1673b96 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationLiveDataContainer.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationLiveDataContainer.android.kt
@@ -17,6 +17,7 @@
package androidx.room
import androidx.lifecycle.LiveData
+import androidx.sqlite.SQLiteConnection
import java.util.Collections
import java.util.IdentityHashMap
import java.util.concurrent.Callable
@@ -33,9 +34,31 @@
fun <T> create(
tableNames: Array<out String>,
inTransaction: Boolean,
- computeFunction: Callable<T?>
+ callableFunction: Callable<T?>
): LiveData<T> {
- return RoomTrackingLiveData(database, this, inTransaction, computeFunction, tableNames)
+ return RoomTrackingLiveData(
+ database = database,
+ container = this,
+ inTransaction = inTransaction,
+ callableFunction = callableFunction,
+ lambdaFunction = null,
+ tableNames = tableNames
+ )
+ }
+
+ fun <T> create(
+ tableNames: Array<out String>,
+ inTransaction: Boolean,
+ lambdaFunction: (SQLiteConnection) -> T?
+ ): LiveData<T> {
+ return RoomTrackingLiveData(
+ database = database,
+ container = this,
+ inTransaction = inTransaction,
+ callableFunction = null,
+ lambdaFunction = lambdaFunction,
+ tableNames = tableNames
+ )
}
fun onActive(liveData: LiveData<*>) {
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
index f4b7057..b030dab 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
@@ -342,6 +342,33 @@
return invalidationLiveDataContainer.create(tableNames, inTransaction, computeFunction)
}
+ /**
+ * Creates a LiveData that computes the given function once and for every other invalidation of
+ * the database.
+ *
+ * Holds a strong reference to the created LiveData as long as it is active.
+ *
+ * @param tableNames The list of tables to observe
+ * @param inTransaction True if the computeFunction will be done in a transaction, false
+ * otherwise.
+ * @param computeFunction The function that calculates the value
+ * @param T The return type
+ * @return A new LiveData that computes the given function when the given list of tables
+ * invalidates.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ fun <T> createLiveData(
+ tableNames: Array<out String>,
+ inTransaction: Boolean,
+ computeFunction: (SQLiteConnection) -> T?
+ ): LiveData<T> {
+ // Validate names early to fail fast as actual observer subscription is done once LiveData
+ // is observed.
+ implementation.validateTableNames(tableNames)
+ // TODO(329315924): Could we use createFlow(...).asLiveData() ?
+ return invalidationLiveDataContainer.create(tableNames, inTransaction, computeFunction)
+ }
+
internal fun initMultiInstanceInvalidation(
context: Context,
name: String,
@@ -399,7 +426,7 @@
*
* This class will automatically unsubscribe when the wrapped observer goes out of memory.
*/
- private class WeakObserver(
+ internal class WeakObserver(
val tracker: InvalidationTracker,
val coroutineScope: CoroutineScope,
delegate: Observer
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomTrackingLiveData.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomTrackingLiveData.android.kt
index 8fa3649..9450a2b 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomTrackingLiveData.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomTrackingLiveData.android.kt
@@ -15,13 +15,13 @@
*/
package androidx.room
-import androidx.arch.core.executor.ArchTaskExecutor
import androidx.lifecycle.LiveData
-import java.lang.Exception
-import java.lang.RuntimeException
+import androidx.room.util.performSuspending
+import androidx.sqlite.SQLiteConnection
import java.util.concurrent.Callable
-import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* A LiveData implementation that closely works with [InvalidationTracker] to implement database
@@ -36,24 +36,32 @@
* [InvalidationTracker] as long as it is active.
*/
internal class RoomTrackingLiveData<T>(
- val database: RoomDatabase,
+ private val database: RoomDatabase,
private val container: InvalidationLiveDataContainer,
- val inTransaction: Boolean,
- val computeFunction: Callable<T?>,
+ private val inTransaction: Boolean,
+ private val callableFunction: Callable<T?>?,
+ private val lambdaFunction: ((SQLiteConnection) -> T?)?,
tableNames: Array<out String>
) : LiveData<T>() {
- val observer: InvalidationTracker.Observer =
+ private val observer: InvalidationTracker.Observer =
object : InvalidationTracker.Observer(tableNames) {
override fun onInvalidated(tables: Set<String>) {
- ArchTaskExecutor.getInstance().executeOnMainThread(invalidationRunnable)
+ database.getCoroutineScope().launch { invalidated() }
}
}
- val invalid = AtomicBoolean(true)
- val computing = AtomicBoolean(false)
- val registeredObserver = AtomicBoolean(false)
- val refreshRunnable = Runnable {
+ private val invalid = AtomicBoolean(true)
+ private val computing = AtomicBoolean(false)
+ private val registeredObserver = AtomicBoolean(false)
+
+ private suspend fun refresh() {
if (registeredObserver.compareAndSet(false, true)) {
- database.invalidationTracker.addWeakObserver(observer)
+ database.invalidationTracker.subscribe(
+ InvalidationTracker.WeakObserver(
+ database.invalidationTracker,
+ database.getCoroutineScope(),
+ observer
+ )
+ )
}
var computed: Boolean
do {
@@ -66,7 +74,22 @@
while (invalid.compareAndSet(true, false)) {
computed = true
try {
- value = computeFunction.call()
+ value =
+ if (callableFunction != null) {
+ withContext(
+ if (inTransaction) {
+ database.getTransactionContext()
+ } else {
+ database.getQueryContext()
+ }
+ ) {
+ callableFunction.call()
+ }
+ } else if (lambdaFunction != null) {
+ performSuspending(database, true, inTransaction, lambdaFunction)
+ } else {
+ error("Both callable and lambda functions are null")
+ }
} catch (e: Exception) {
throw RuntimeException(
"Exception while computing database live data.",
@@ -92,33 +115,23 @@
} while (computed && invalid.get())
}
- val invalidationRunnable = Runnable {
+ private suspend fun invalidated() {
val isActive = hasActiveObservers()
if (invalid.compareAndSet(false, true)) {
if (isActive) {
- queryExecutor.execute(refreshRunnable)
+ refresh()
}
}
}
- @Suppress("UNCHECKED_CAST")
override fun onActive() {
super.onActive()
- container.onActive(this as LiveData<Any>)
- queryExecutor.execute(refreshRunnable)
+ container.onActive(this)
+ database.getCoroutineScope().launch { refresh() }
}
- @Suppress("UNCHECKED_CAST")
override fun onInactive() {
super.onInactive()
- container.onInactive(this as LiveData<Any>)
+ container.onInactive(this)
}
-
- val queryExecutor: Executor
- get() =
- if (inTransaction) {
- database.transactionExecutor
- } else {
- database.queryExecutor
- }
}
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorDatabase.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorDatabase.android.kt
index c6eb76d..58dce9f 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorDatabase.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorDatabase.android.kt
@@ -51,7 +51,7 @@
override fun beginTransactionNonExclusive() {
queryCallbackScope.launch {
- queryCallback.onQuery("BEGIN DEFERRED TRANSACTION", emptyList())
+ queryCallback.onQuery("BEGIN IMMEDIATE TRANSACTION", emptyList())
}
delegate.beginTransactionNonExclusive()
}
@@ -67,7 +67,7 @@
transactionListener: SQLiteTransactionListener
) {
queryCallbackScope.launch {
- queryCallback.onQuery("BEGIN DEFERRED TRANSACTION", emptyList())
+ queryCallback.onQuery("BEGIN IMMEDIATE TRANSACTION", emptyList())
}
delegate.beginTransactionWithListenerNonExclusive(transactionListener)
}
diff --git a/room/room-rxjava2/api/current.txt b/room/room-rxjava2/api/current.txt
index 73d416e..4df9b3d 100644
--- a/room/room-rxjava2/api/current.txt
+++ b/room/room-rxjava2/api/current.txt
@@ -2,14 +2,20 @@
package androidx.room {
public class EmptyResultSetException extends java.lang.RuntimeException {
- ctor public EmptyResultSetException(String!);
+ ctor public EmptyResultSetException(String message);
}
public class RxRoom {
ctor @Deprecated public RxRoom();
- method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...!);
- method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...!);
- field public static final Object! NOTHING;
+ method public static final io.reactivex.Flowable<java.lang.Object> createFlowable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+ method public static final io.reactivex.Observable<java.lang.Object> createObservable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+ field public static final androidx.room.RxRoom.Companion Companion;
+ field public static final Object NOTHING;
+ }
+
+ public static final class RxRoom.Companion {
+ method public io.reactivex.Flowable<java.lang.Object> createFlowable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+ method public io.reactivex.Observable<java.lang.Object> createObservable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
}
}
diff --git a/room/room-rxjava2/api/restricted_current.txt b/room/room-rxjava2/api/restricted_current.txt
index a3ecfa2..9a2d59c 100644
--- a/room/room-rxjava2/api/restricted_current.txt
+++ b/room/room-rxjava2/api/restricted_current.txt
@@ -2,19 +2,40 @@
package androidx.room {
public class EmptyResultSetException extends java.lang.RuntimeException {
- ctor public EmptyResultSetException(String!);
+ ctor public EmptyResultSetException(String message);
}
public class RxRoom {
ctor @Deprecated public RxRoom();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T!>! createFlowable(androidx.room.RoomDatabase!, boolean, String![]!, java.util.concurrent.Callable<T!>!);
- method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...!);
- method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T!>! createFlowable(androidx.room.RoomDatabase!, String![]!, java.util.concurrent.Callable<T!>!);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T!>! createObservable(androidx.room.RoomDatabase!, boolean, String![]!, java.util.concurrent.Callable<T!>!);
- method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...!);
- method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T!>! createObservable(androidx.room.RoomDatabase!, String![]!, java.util.concurrent.Callable<T!>!);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Single<T!>! createSingle(java.util.concurrent.Callable<? extends T!>!);
- field public static final Object! NOTHING;
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final io.reactivex.Completable createCompletable(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,kotlin.Unit> block);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Flowable<T> createFlowable(androidx.room.RoomDatabase database, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Flowable<T> createFlowable(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+ method public static final io.reactivex.Flowable<java.lang.Object> createFlowable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Flowable<T> createFlowable(androidx.room.RoomDatabase database, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Maybe<T> createMaybe(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Observable<T> createObservable(androidx.room.RoomDatabase database, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Observable<T> createObservable(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+ method public static final io.reactivex.Observable<java.lang.Object> createObservable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Observable<T> createObservable(androidx.room.RoomDatabase database, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Single<T> createSingle(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Single<T> createSingle(java.util.concurrent.Callable<? extends T> callable);
+ field public static final androidx.room.RxRoom.Companion Companion;
+ field public static final Object NOTHING;
+ }
+
+ public static final class RxRoom.Companion {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public io.reactivex.Completable createCompletable(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,kotlin.Unit> block);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Flowable<T> createFlowable(androidx.room.RoomDatabase database, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Flowable<T> createFlowable(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+ method public io.reactivex.Flowable<java.lang.Object> createFlowable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Flowable<T> createFlowable(androidx.room.RoomDatabase database, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Maybe<T> createMaybe(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Observable<T> createObservable(androidx.room.RoomDatabase database, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Observable<T> createObservable(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+ method public io.reactivex.Observable<java.lang.Object> createObservable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Observable<T> createObservable(androidx.room.RoomDatabase database, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Single<T> createSingle(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Single<T> createSingle(java.util.concurrent.Callable<? extends T> callable);
}
}
diff --git a/room/room-rxjava2/build.gradle b/room/room-rxjava2/build.gradle
index c49ef63..2b23d3f 100644
--- a/room/room-rxjava2/build.gradle
+++ b/room/room-rxjava2/build.gradle
@@ -36,6 +36,7 @@
implementation("androidx.arch.core:core-runtime:2.2.0")
implementation(libs.kotlinStdlib)
+ implementation(libs.kotlinCoroutinesRx2)
testImplementation(project(":kruth:kruth"))
testImplementation(libs.kotlinTest)
diff --git a/room/room-rxjava2/src/main/java/androidx/room/EmptyResultSetException.java b/room/room-rxjava2/src/main/java/androidx/room/EmptyResultSetException.java
deleted file mode 100644
index a36d8a9..0000000
--- a/room/room-rxjava2/src/main/java/androidx/room/EmptyResultSetException.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room;
-
-/**
- * Thrown by Room when the query in a Single<T> DAO method needs to return a result but the
- * returned result from the database is empty.
- * <p>
- * Since a Single<T> must either emit a single non-null value or an error, this exception is
- * thrown instead of emitting a null value when the query resulted empty. If the Single<T>
- * contains a type argument of a collection (e.g. Single<List<Song>>) then this
- * exception is not thrown an an empty collection is emitted instead.
- */
-public class EmptyResultSetException extends RuntimeException {
- /**
- * Constructs a new EmptyResultSetException with the exception.
- * @param message The SQL query which didn't return any results.
- */
- public EmptyResultSetException(String message) {
- super(message);
- }
-}
diff --git a/room/room-rxjava2/src/main/java/androidx/room/EmptyResultSetException.kt b/room/room-rxjava2/src/main/java/androidx/room/EmptyResultSetException.kt
new file mode 100644
index 0000000..e2674f6
--- /dev/null
+++ b/room/room-rxjava2/src/main/java/androidx/room/EmptyResultSetException.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room
+
+/**
+ * Thrown by Room when the query in a [io.reactivex.Single] DAO method needs to return a result but
+ * the returned result from the database is empty.
+ *
+ * Since a [io.reactivex.Single] must either emit a single non-null value or an error, this
+ * exception is thrown instead of emitting a null value when the query resulted empty. If the
+ * [io.reactivex.Single] contains a type argument of a collection (e.g. `Single<List<Song>>`) the
+ * this exception is not thrown an an empty collection is emitted instead.
+ */
+open class EmptyResultSetException(message: String) : RuntimeException(message)
diff --git a/room/room-rxjava2/src/main/java/androidx/room/RxRoom.java b/room/room-rxjava2/src/main/java/androidx/room/RxRoom.java
deleted file mode 100644
index 42f3f79..0000000
--- a/room/room-rxjava2/src/main/java/androidx/room/RxRoom.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room;
-
-import android.annotation.SuppressLint;
-
-import androidx.annotation.RestrictTo;
-
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executor;
-
-import io.reactivex.BackpressureStrategy;
-import io.reactivex.Flowable;
-import io.reactivex.FlowableEmitter;
-import io.reactivex.FlowableOnSubscribe;
-import io.reactivex.Maybe;
-import io.reactivex.MaybeSource;
-import io.reactivex.Observable;
-import io.reactivex.ObservableEmitter;
-import io.reactivex.ObservableOnSubscribe;
-import io.reactivex.Scheduler;
-import io.reactivex.Single;
-import io.reactivex.SingleEmitter;
-import io.reactivex.SingleOnSubscribe;
-import io.reactivex.disposables.Disposables;
-import io.reactivex.functions.Action;
-import io.reactivex.functions.Function;
-import io.reactivex.schedulers.Schedulers;
-
-/**
- * Helper class to add RxJava2 support to Room.
- */
-@SuppressLint("PrivateConstructorForUtilityClass")
-@SuppressWarnings("WeakerAccess")
-public class RxRoom {
- /**
- * Data dispatched by the publisher created by {@link #createFlowable(RoomDatabase, String...)}.
- */
- public static final Object NOTHING = new Object();
-
- /**
- * Creates a {@link Flowable} that emits at least once and also re-emits whenever one of the
- * observed tables is updated.
- * <p>
- * You can easily chain a database operation to downstream of this {@link Flowable} to ensure
- * that it re-runs when database is modified.
- * <p>
- * Since database invalidation is batched, multiple changes in the database may results in just
- * 1 emission.
- *
- * @param database The database instance
- * @param tableNames The list of table names that should be observed
- * @return A {@link Flowable} which emits {@link #NOTHING} when one of the observed tables
- * is modified (also once when the invalidation tracker connection is established).
- */
- public static Flowable<Object> createFlowable(final RoomDatabase database,
- final String... tableNames) {
- return Flowable.create(new FlowableOnSubscribe<Object>() {
- @Override
- public void subscribe(final FlowableEmitter<Object> emitter) throws Exception {
- final InvalidationTracker.Observer observer = new InvalidationTracker.Observer(
- tableNames) {
- @Override
- public void onInvalidated(@androidx.annotation.NonNull Set<String> tables) {
- if (!emitter.isCancelled()) {
- emitter.onNext(NOTHING);
- }
- }
- };
- if (!emitter.isCancelled()) {
- database.getInvalidationTracker().addObserver(observer);
- emitter.setDisposable(Disposables.fromAction(new Action() {
- @Override
- public void run() throws Exception {
- database.getInvalidationTracker().removeObserver(observer);
- }
- }));
- }
-
- // emit once to avoid missing any data and also easy chaining
- if (!emitter.isCancelled()) {
- emitter.onNext(NOTHING);
- }
- }
- }, BackpressureStrategy.LATEST);
- }
-
- /**
- * Helper method used by generated code to bind a Callable such that it will be run in
- * our disk io thread and will automatically block null values since RxJava2 does not like null.
- *
- * @deprecated Use {@link #createFlowable(RoomDatabase, boolean, String[], Callable)}
- *
- */
- @Deprecated
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public static <T> Flowable<T> createFlowable(final RoomDatabase database,
- final String[] tableNames, final Callable<T> callable) {
- return createFlowable(database, false, tableNames, callable);
- }
-
- /**
- * Helper method used by generated code to bind a Callable such that it will be run in
- * our disk io thread and will automatically block null values since RxJava2 does not like null.
- *
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public static <T> Flowable<T> createFlowable(final RoomDatabase database,
- final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
- Scheduler scheduler = Schedulers.from(getExecutor(database, inTransaction));
- final Maybe<T> maybe = Maybe.fromCallable(callable);
- return createFlowable(database, tableNames)
- .subscribeOn(scheduler)
- .unsubscribeOn(scheduler)
- .observeOn(scheduler)
- .flatMapMaybe(new Function<Object, MaybeSource<T>>() {
- @Override
- public MaybeSource<T> apply(Object o) throws Exception {
- return maybe;
- }
- });
- }
-
- /**
- * Creates a {@link Observable} that emits at least once and also re-emits whenever one of the
- * observed tables is updated.
- * <p>
- * You can easily chain a database operation to downstream of this {@link Observable} to ensure
- * that it re-runs when database is modified.
- * <p>
- * Since database invalidation is batched, multiple changes in the database may results in just
- * 1 emission.
- *
- * @param database The database instance
- * @param tableNames The list of table names that should be observed
- * @return A {@link Observable} which emits {@link #NOTHING} when one of the observed tables
- * is modified (also once when the invalidation tracker connection is established).
- */
- public static Observable<Object> createObservable(final RoomDatabase database,
- final String... tableNames) {
- return Observable.create(new ObservableOnSubscribe<Object>() {
- @Override
- public void subscribe(final ObservableEmitter<Object> emitter) throws Exception {
- final InvalidationTracker.Observer observer = new InvalidationTracker.Observer(
- tableNames) {
- @Override
- public void onInvalidated(@androidx.annotation.NonNull Set<String> tables) {
- emitter.onNext(NOTHING);
- }
- };
- database.getInvalidationTracker().addObserver(observer);
- emitter.setDisposable(Disposables.fromAction(new Action() {
- @Override
- public void run() throws Exception {
- database.getInvalidationTracker().removeObserver(observer);
- }
- }));
-
- // emit once to avoid missing any data and also easy chaining
- emitter.onNext(NOTHING);
- }
- });
- }
-
- /**
- * Helper method used by generated code to bind a Callable such that it will be run in
- * our disk io thread and will automatically block null values since RxJava2 does not like null.
- *
- * @deprecated Use {@link #createObservable(RoomDatabase, boolean, String[], Callable)}
- *
- */
- @Deprecated
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public static <T> Observable<T> createObservable(final RoomDatabase database,
- final String[] tableNames, final Callable<T> callable) {
- return createObservable(database, false, tableNames, callable);
- }
-
- /**
- * Helper method used by generated code to bind a Callable such that it will be run in
- * our disk io thread and will automatically block null values since RxJava2 does not like null.
- *
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public static <T> Observable<T> createObservable(final RoomDatabase database,
- final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
- Scheduler scheduler = Schedulers.from(getExecutor(database, inTransaction));
- final Maybe<T> maybe = Maybe.fromCallable(callable);
- return createObservable(database, tableNames)
- .subscribeOn(scheduler)
- .unsubscribeOn(scheduler)
- .observeOn(scheduler)
- .flatMapMaybe(new Function<Object, MaybeSource<T>>() {
- @Override
- public MaybeSource<T> apply(Object o) throws Exception {
- return maybe;
- }
- });
- }
-
- /**
- * Helper method used by generated code to create a Single from a Callable that will ignore
- * the EmptyResultSetException if the stream is already disposed.
- *
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public static <T> Single<T> createSingle(final Callable<? extends T> callable) {
- return Single.create(new SingleOnSubscribe<T>() {
- @Override
- public void subscribe(SingleEmitter<T> emitter) throws Exception {
- try {
- emitter.onSuccess(callable.call());
- } catch (EmptyResultSetException e) {
- emitter.tryOnError(e);
- }
- }
- });
- }
-
- private static Executor getExecutor(RoomDatabase database, boolean inTransaction) {
- if (inTransaction) {
- return database.getTransactionExecutor();
- } else {
- return database.getQueryExecutor();
- }
- }
-
- /** @deprecated This type should not be instantiated as it contains only static methods. */
- @Deprecated
- @SuppressWarnings("PrivateConstructorForUtilityClass")
- public RxRoom() {
- }
-}
diff --git a/room/room-rxjava2/src/main/java/androidx/room/RxRoom.kt b/room/room-rxjava2/src/main/java/androidx/room/RxRoom.kt
new file mode 100644
index 0000000..2ae5937
--- /dev/null
+++ b/room/room-rxjava2/src/main/java/androidx/room/RxRoom.kt
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room
+
+import androidx.annotation.RestrictTo
+import androidx.room.coroutines.createFlow
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteConnection
+import io.reactivex.BackpressureStrategy
+import io.reactivex.Completable
+import io.reactivex.Flowable
+import io.reactivex.Maybe
+import io.reactivex.Observable
+import io.reactivex.Single
+import io.reactivex.disposables.Disposables
+import io.reactivex.schedulers.Schedulers
+import java.util.concurrent.Callable
+import java.util.concurrent.Executor
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.rx2.asObservable
+
+open class RxRoom
+@Deprecated("This type should not be instantiated as it contains only utility functions.")
+constructor() {
+
+ companion object {
+
+ /** Data dispatched by the publisher created by [createFlowable]. */
+ @JvmField val NOTHING: Any = Any()
+
+ /** Helper function used by generated code to create a [Flowable] */
+ @JvmStatic
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ fun <T : Any> createFlowable(
+ db: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ block: (SQLiteConnection) -> T?
+ ): Flowable<T> =
+ createObservable(db, inTransaction, tableNames, block)
+ .toFlowable(BackpressureStrategy.LATEST)
+
+ /** Helper function used by generated code to create a [Observable] */
+ @JvmStatic
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ fun <T : Any> createObservable(
+ db: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ block: (SQLiteConnection) -> T?
+ ): Observable<T> =
+ createFlow(db, inTransaction, tableNames, block)
+ .filterNotNull()
+ .asObservable(db.getQueryContext())
+
+ /** Helper function used by generated code to create a [Maybe] */
+ @JvmStatic
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ fun <T : Any> createMaybe(
+ db: RoomDatabase,
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> T?
+ ): Maybe<T> = Maybe.fromCallable { performBlocking(db, isReadOnly, inTransaction, block) }
+
+ /** Helper function used by generated code to create a [Completable] */
+ @JvmStatic
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ fun createCompletable(
+ db: RoomDatabase,
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> Unit
+ ): Completable =
+ Completable.fromAction { performBlocking(db, isReadOnly, inTransaction, block) }
+
+ /** Helper function used by generated code to create a [Single] */
+ @JvmStatic
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ fun <T : Any> createSingle(
+ db: RoomDatabase,
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> T?
+ ): Single<T> =
+ Single.create { emitter ->
+ if (emitter.isDisposed) return@create
+ try {
+ val result = performBlocking(db, isReadOnly, inTransaction, block)
+ if (result != null) {
+ emitter.onSuccess(result)
+ } else {
+ throw EmptyResultSetException("Query returned empty result set.")
+ }
+ } catch (e: EmptyResultSetException) {
+ emitter.tryOnError(e)
+ }
+ }
+
+ /**
+ * Creates a [Flowable] that emits at least once and also re-emits whenever one of the
+ * observed tables is updated.
+ *
+ * You can easily chain a database operation to downstream of this [Flowable] to ensure that
+ * it re-runs when database is modified.
+ *
+ * Since database invalidation is batched, multiple changes in the database may results in
+ * just 1 emission.
+ *
+ * @param database The database instance
+ * @param tableNames The list of table names that should be observed
+ * @return A [Flowable] which emits [NOTHING] when one of the observed tables is modified
+ * (also once when the invalidation tracker connection is established).
+ */
+ @JvmStatic
+ fun createFlowable(database: RoomDatabase, vararg tableNames: String): Flowable<Any> {
+ return Flowable.create(
+ { emitter ->
+ val observer =
+ object : InvalidationTracker.Observer(tableNames) {
+ override fun onInvalidated(tables: Set<String>) {
+ if (!emitter.isCancelled) {
+ emitter.onNext(NOTHING)
+ }
+ }
+ }
+ if (!emitter.isCancelled) {
+ database.invalidationTracker.addObserver(observer)
+ emitter.setDisposable(
+ Disposables.fromAction {
+ database.invalidationTracker.removeObserver(observer)
+ }
+ )
+ }
+
+ // emit once to avoid missing any data and also easy chaining
+ if (!emitter.isCancelled) {
+ emitter.onNext(NOTHING)
+ }
+ },
+ BackpressureStrategy.LATEST
+ )
+ }
+
+ /**
+ * Helper method used by generated code to bind a [Callable] such that it will be run in our
+ * disk io thread and will automatically block null values since RxJava2 does not like null.
+ */
+ @JvmStatic
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ @Deprecated("No longer used by generated code.")
+ fun <T : Any> createFlowable(
+ database: RoomDatabase,
+ tableNames: Array<String>,
+ callable: Callable<out T>
+ ): Flowable<T> {
+ @Suppress("DEPRECATION") return createFlowable(database, false, tableNames, callable)
+ }
+
+ /**
+ * Helper method used by generated code to bind a [Callable] such that it will be run in our
+ * disk io thread and will automatically block null values since RxJava2 does not like null.
+ */
+ @JvmStatic
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ @Deprecated("No longer used by generated code.")
+ fun <T : Any> createFlowable(
+ database: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ callable: Callable<out T>
+ ): Flowable<T> {
+ val scheduler = Schedulers.from(getExecutor(database, inTransaction))
+ val maybe = Maybe.fromCallable(callable)
+ return createFlowable(database, *tableNames)
+ .subscribeOn(scheduler)
+ .unsubscribeOn(scheduler)
+ .observeOn(scheduler)
+ .flatMapMaybe { maybe }
+ }
+
+ /**
+ * Creates a [Observable] that emits at least once and also re-emits whenever one of the
+ * observed tables is updated.
+ *
+ * You can easily chain a database operation to downstream of this [Observable] to ensure
+ * that it re-runs when database is modified.
+ *
+ * Since database invalidation is batched, multiple changes in the database may results in
+ * just 1 emission.
+ *
+ * @param database The database instance
+ * @param tableNames The list of table names that should be observed
+ * @return A [Observable] which emits [.NOTHING] when one of the observed tables is modified
+ * (also once when the invalidation tracker connection is established).
+ */
+ @JvmStatic
+ fun createObservable(database: RoomDatabase, vararg tableNames: String): Observable<Any> {
+ return Observable.create { emitter ->
+ val observer =
+ object : InvalidationTracker.Observer(tableNames) {
+ override fun onInvalidated(tables: Set<String>) {
+ emitter.onNext(NOTHING)
+ }
+ }
+ database.invalidationTracker.addObserver(observer)
+ emitter.setDisposable(
+ Disposables.fromAction { database.invalidationTracker.removeObserver(observer) }
+ )
+
+ // emit once to avoid missing any data and also easy chaining
+ emitter.onNext(NOTHING)
+ }
+ }
+
+ /**
+ * Helper method used by generated code to bind a [Callable] such that it will be run in our
+ * disk io thread and will automatically block null values since RxJava2 does not like null.
+ */
+ @JvmStatic
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ @Deprecated("No longer used by generated code.")
+ fun <T : Any> createObservable(
+ database: RoomDatabase,
+ tableNames: Array<String>,
+ callable: Callable<out T>
+ ): Observable<T> {
+ @Suppress("DEPRECATION") return createObservable(database, false, tableNames, callable)
+ }
+
+ /**
+ * Helper method used by generated code to bind a [Callable] such that it will be run in our
+ * disk io thread and will automatically block null values since RxJava2 does not like null.
+ */
+ @JvmStatic
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ @Deprecated("No longer used by generated code.")
+ fun <T : Any> createObservable(
+ database: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ callable: Callable<out T>
+ ): Observable<T> {
+ val scheduler = Schedulers.from(getExecutor(database, inTransaction))
+ val maybe = Maybe.fromCallable(callable)
+ return createObservable(database, *tableNames)
+ .subscribeOn(scheduler)
+ .unsubscribeOn(scheduler)
+ .observeOn(scheduler)
+ .flatMapMaybe { maybe }
+ }
+
+ /**
+ * Helper method used by generated code to create a [Single] from a [Callable] that will
+ * ignore the [EmptyResultSetException] if the stream is already disposed.
+ */
+ @JvmStatic
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ fun <T : Any> createSingle(callable: Callable<out T>): Single<T> {
+ return Single.create { emitter ->
+ try {
+ val result = callable.call()
+ if (result != null) {
+ emitter.onSuccess(result)
+ } else {
+ throw EmptyResultSetException("Query returned empty result set.")
+ }
+ } catch (e: EmptyResultSetException) {
+ emitter.tryOnError(e)
+ }
+ }
+ }
+
+ private fun getExecutor(database: RoomDatabase, inTransaction: Boolean): Executor {
+ return if (inTransaction) {
+ database.transactionExecutor
+ } else {
+ database.queryExecutor
+ }
+ }
+ }
+}
diff --git a/room/room-rxjava2/src/test/java/androidx/room/RxRoomTest.kt b/room/room-rxjava2/src/test/java/androidx/room/RxRoomTest.kt
index 32b23a8..2dcc9d5 100644
--- a/room/room-rxjava2/src/test/java/androidx/room/RxRoomTest.kt
+++ b/room/room-rxjava2/src/test/java/androidx/room/RxRoomTest.kt
@@ -21,6 +21,7 @@
import io.reactivex.functions.Consumer
import io.reactivex.observers.TestObserver
import io.reactivex.subscribers.TestSubscriber
+import java.util.concurrent.Callable
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
import org.junit.Before
@@ -138,12 +139,12 @@
}
@Test
- @Throws(Exception::class)
+ @Suppress("DEPRECATION")
fun internalCallable_Flowable() {
val value = AtomicReference<Any>(null)
val tables = arrayOf("a", "b")
val tableSet: Set<String> = HashSet(listOf(*tables))
- val flowable = RxRoom.createFlowable(mDatabase, false, tables) { value.get() }
+ val flowable = RxRoom.createFlowable(mDatabase, false, tables, Callable { value.get() })
val consumer = CountingConsumer()
flowable.subscribe(consumer)
drain()
@@ -167,12 +168,12 @@
}
@Test
- @Throws(Exception::class)
+ @Suppress("DEPRECATION")
fun internalCallable_Observable() {
val value = AtomicReference<Any>(null)
val tables = arrayOf("a", "b")
val tableSet: Set<String> = HashSet(listOf(*tables))
- val flowable = RxRoom.createObservable(mDatabase, false, tables) { value.get() }
+ val flowable = RxRoom.createObservable(mDatabase, false, tables, Callable { value.get() })
val consumer = CountingConsumer()
flowable.subscribe(consumer)
drain()
@@ -196,11 +197,15 @@
}
@Test
+ @Suppress("DEPRECATION")
fun exception_Flowable() {
val flowable =
- RxRoom.createFlowable<String>(mDatabase, false, arrayOf("a")) {
- throw Exception("i want exception")
- }
+ RxRoom.createFlowable<String>(
+ mDatabase,
+ false,
+ arrayOf("a"),
+ Callable { throw Exception("i want exception") }
+ )
val subscriber = TestSubscriber<String>()
flowable.subscribe(subscriber)
drain()
@@ -209,11 +214,15 @@
}
@Test
+ @Suppress("DEPRECATION")
fun exception_Observable() {
val flowable =
- RxRoom.createObservable<String>(mDatabase, false, arrayOf("a")) {
- throw Exception("i want exception")
- }
+ RxRoom.createObservable<String>(
+ mDatabase,
+ false,
+ arrayOf("a"),
+ Callable { throw Exception("i want exception") }
+ )
val observer = TestObserver<String>()
flowable.subscribe(observer)
drain()
diff --git a/room/room-rxjava3/api/current.txt b/room/room-rxjava3/api/current.txt
index 6b78281..bbddba6 100644
--- a/room/room-rxjava3/api/current.txt
+++ b/room/room-rxjava3/api/current.txt
@@ -2,12 +2,12 @@
package androidx.room.rxjava3 {
public final class EmptyResultSetException extends java.lang.RuntimeException {
- ctor public EmptyResultSetException(String);
+ ctor public EmptyResultSetException(String message);
}
public final class RxRoom {
- method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object!> createFlowable(androidx.room.RoomDatabase, java.lang.String!...);
- method public static io.reactivex.rxjava3.core.Observable<java.lang.Object!> createObservable(androidx.room.RoomDatabase, java.lang.String!...);
+ method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object> createFlowable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+ method public static io.reactivex.rxjava3.core.Observable<java.lang.Object> createObservable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
field public static final Object NOTHING;
}
diff --git a/room/room-rxjava3/api/restricted_current.txt b/room/room-rxjava3/api/restricted_current.txt
index 4fb4dbd..9b71011 100644
--- a/room/room-rxjava3/api/restricted_current.txt
+++ b/room/room-rxjava3/api/restricted_current.txt
@@ -2,15 +2,20 @@
package androidx.room.rxjava3 {
public final class EmptyResultSetException extends java.lang.RuntimeException {
- ctor public EmptyResultSetException(String);
+ ctor public EmptyResultSetException(String message);
}
public final class RxRoom {
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Flowable<T!> createFlowable(androidx.room.RoomDatabase, boolean, String![], java.util.concurrent.Callable<T!>);
- method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object!> createFlowable(androidx.room.RoomDatabase, java.lang.String!...);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Observable<T!> createObservable(androidx.room.RoomDatabase, boolean, String![], java.util.concurrent.Callable<T!>);
- method public static io.reactivex.rxjava3.core.Observable<java.lang.Object!> createObservable(androidx.room.RoomDatabase, java.lang.String!...);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Single<T!> createSingle(java.util.concurrent.Callable<? extends T!>);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static io.reactivex.rxjava3.core.Completable createCompletable(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,kotlin.Unit> block);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Flowable<T> createFlowable(androidx.room.RoomDatabase database, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Flowable<T> createFlowable(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+ method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object> createFlowable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Maybe<T> createMaybe(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Observable<T> createObservable(androidx.room.RoomDatabase database, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Observable<T> createObservable(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+ method public static io.reactivex.rxjava3.core.Observable<java.lang.Object> createObservable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Single<T> createSingle(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Single<T> createSingle(java.util.concurrent.Callable<? extends T> callable);
field public static final Object NOTHING;
}
diff --git a/room/room-rxjava3/build.gradle b/room/room-rxjava3/build.gradle
index dd068ad..96c86a1 100644
--- a/room/room-rxjava3/build.gradle
+++ b/room/room-rxjava3/build.gradle
@@ -27,7 +27,6 @@
id("AndroidXPlugin")
id("com.android.library")
id("kotlin-android")
- id("com.google.devtools.ksp")
}
dependencies {
@@ -37,6 +36,7 @@
implementation("androidx.arch.core:core-runtime:2.2.0")
implementation(libs.kotlinStdlib)
+ implementation(libs.kotlinCoroutinesRx3)
testImplementation(project(":kruth:kruth"))
testImplementation(libs.kotlinTest)
diff --git a/room/room-rxjava3/src/main/java/androidx/room/rxjava3/EmptyResultSetException.java b/room/room-rxjava3/src/main/java/androidx/room/rxjava3/EmptyResultSetException.java
deleted file mode 100644
index 3b2058d..0000000
--- a/room/room-rxjava3/src/main/java/androidx/room/rxjava3/EmptyResultSetException.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room.rxjava3;
-
-import androidx.annotation.NonNull;
-
-/**
- * Thrown by Room when the query in a Single<T> DAO method needs to return a result but the
- * returned result from the database is empty.
- * <p>
- * Since a Single<T> must either emit a single non-null value or an error, this exception is
- * thrown instead of emitting a null value when the query resulted empty. If the Single<T>
- * contains a type argument of a collection (e.g. Single<List<Song>>) then this
- * exception is not thrown an an empty collection is emitted instead.
- */
-@SuppressWarnings("serial")
-public final class EmptyResultSetException extends RuntimeException {
- /**
- * Constructs a new EmptyResultSetException with the exception.
- * @param message The SQL query which didn't return any results.
- */
- public EmptyResultSetException(@NonNull String message) {
- super(message);
- }
-}
diff --git a/room/room-rxjava3/src/main/java/androidx/room/rxjava3/EmptyResultSetException.kt b/room/room-rxjava3/src/main/java/androidx/room/rxjava3/EmptyResultSetException.kt
new file mode 100644
index 0000000..b009f6e
--- /dev/null
+++ b/room/room-rxjava3/src/main/java/androidx/room/rxjava3/EmptyResultSetException.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.rxjava3
+
+/**
+ * Thrown by Room when the query in a [io.reactivex.rxjava3.core.Single] DAO method needs to return
+ * a result but the returned result from the database is empty.
+ *
+ * Since a [io.reactivex.rxjava3.core.Single] must either emit a single non-null value or an error,
+ * this exception is thrown instead of emitting a null value when the query resulted empty. If the
+ * [io.reactivex.rxjava3.core.Single] contains a type argument of a collection (e.g.
+ * `Single<List<Song>>`) the this exception is not thrown an an empty collection is emitted instead.
+ */
+class EmptyResultSetException(message: String) : RuntimeException(message)
diff --git a/room/room-rxjava3/src/main/java/androidx/room/rxjava3/RxRoom.java b/room/room-rxjava3/src/main/java/androidx/room/rxjava3/RxRoom.java
deleted file mode 100644
index e520efd..0000000
--- a/room/room-rxjava3/src/main/java/androidx/room/rxjava3/RxRoom.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room.rxjava3;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.room.InvalidationTracker;
-import androidx.room.RoomDatabase;
-
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executor;
-
-import io.reactivex.rxjava3.core.BackpressureStrategy;
-import io.reactivex.rxjava3.core.Flowable;
-import io.reactivex.rxjava3.core.Maybe;
-import io.reactivex.rxjava3.core.MaybeSource;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Scheduler;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.disposables.Disposable;
-import io.reactivex.rxjava3.functions.Function;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-/**
- * Helper class to add RxJava3 support to Room.
- */
-public final class RxRoom {
- /**
- * Data dispatched by the publisher created by {@link #createFlowable(RoomDatabase, String...)}.
- */
- @NonNull
- public static final Object NOTHING = new Object();
-
- /**
- * Creates a {@link Flowable} that emits at least once and also re-emits whenever one of the
- * observed tables is updated.
- * <p>
- * You can easily chain a database operation to downstream of this {@link Flowable} to ensure
- * that it re-runs when database is modified.
- * <p>
- * Since database invalidation is batched, multiple changes in the database may results in just
- * 1 emission.
- *
- * @param database The database instance
- * @param tableNames The list of table names that should be observed
- * @return A {@link Flowable} which emits {@link #NOTHING} when one of the observed tables
- * is modified (also once when the invalidation tracker connection is established).
- */
- @NonNull
- public static Flowable<Object> createFlowable(@NonNull final RoomDatabase database,
- @NonNull final String... tableNames) {
- return Flowable.create(emitter -> {
- final InvalidationTracker.Observer observer = new InvalidationTracker.Observer(
- tableNames) {
- @Override
- public void onInvalidated(@androidx.annotation.NonNull Set<String> tables) {
- if (!emitter.isCancelled()) {
- emitter.onNext(NOTHING);
- }
- }
- };
- if (!emitter.isCancelled()) {
- database.getInvalidationTracker().addObserver(observer);
- emitter.setDisposable(Disposable.fromAction(
- () -> database.getInvalidationTracker().removeObserver(observer)));
- }
-
- // emit once to avoid missing any data and also easy chaining
- if (!emitter.isCancelled()) {
- emitter.onNext(NOTHING);
- }
- }, BackpressureStrategy.LATEST);
- }
-
- /**
- * Helper method used by generated code to bind a Callable such that it will be run in
- * our disk io thread and will automatically block null values since RxJava3 does not like null.
- *
- */
- @NonNull
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public static <T> Flowable<T> createFlowable(@NonNull final RoomDatabase database,
- final boolean inTransaction, @NonNull final String[] tableNames,
- @NonNull final Callable<T> callable) {
- Scheduler scheduler = Schedulers.from(getExecutor(database, inTransaction));
- final Maybe<T> maybe = Maybe.fromCallable(callable);
- return createFlowable(database, tableNames)
- .subscribeOn(scheduler)
- .unsubscribeOn(scheduler)
- .observeOn(scheduler)
- .flatMapMaybe((Function<Object, MaybeSource<T>>) o -> maybe);
- }
-
- /**
- * Creates a {@link Observable} that emits at least once and also re-emits whenever one of the
- * observed tables is updated.
- * <p>
- * You can easily chain a database operation to downstream of this {@link Observable} to ensure
- * that it re-runs when database is modified.
- * <p>
- * Since database invalidation is batched, multiple changes in the database may results in just
- * 1 emission.
- *
- * @param database The database instance
- * @param tableNames The list of table names that should be observed
- * @return A {@link Observable} which emits {@link #NOTHING} when one of the observed tables
- * is modified (also once when the invalidation tracker connection is established).
- */
- @NonNull
- public static Observable<Object> createObservable(@NonNull final RoomDatabase database,
- @NonNull final String... tableNames) {
- return Observable.create(emitter -> {
- final InvalidationTracker.Observer observer = new InvalidationTracker.Observer(
- tableNames) {
- @Override
- public void onInvalidated(@androidx.annotation.NonNull Set<String> tables) {
- emitter.onNext(NOTHING);
- }
- };
- database.getInvalidationTracker().addObserver(observer);
- emitter.setDisposable(Disposable.fromAction(
- () -> database.getInvalidationTracker().removeObserver(observer)));
-
- // emit once to avoid missing any data and also easy chaining
- emitter.onNext(NOTHING);
- });
- }
-
- /**
- * Helper method used by generated code to bind a Callable such that it will be run in
- * our disk io thread and will automatically block null values since RxJava3 does not like null.
- *
- */
- @NonNull
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public static <T> Observable<T> createObservable(@NonNull final RoomDatabase database,
- final boolean inTransaction, @NonNull final String[] tableNames,
- @NonNull final Callable<T> callable) {
- Scheduler scheduler = Schedulers.from(getExecutor(database, inTransaction));
- final Maybe<T> maybe = Maybe.fromCallable(callable);
- return createObservable(database, tableNames)
- .subscribeOn(scheduler)
- .unsubscribeOn(scheduler)
- .observeOn(scheduler)
- .flatMapMaybe(o -> maybe);
- }
-
- /**
- * Helper method used by generated code to create a Single from a Callable that will ignore
- * the EmptyResultSetException if the stream is already disposed.
- *
- */
- @NonNull
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public static <T> Single<T> createSingle(@NonNull final Callable<? extends T> callable) {
- return Single.create(emitter -> {
- try {
- emitter.onSuccess(callable.call());
- } catch (EmptyResultSetException e) {
- emitter.tryOnError(e);
- }
- });
- }
-
- private static Executor getExecutor(@NonNull RoomDatabase database, boolean inTransaction) {
- if (inTransaction) {
- return database.getTransactionExecutor();
- } else {
- return database.getQueryExecutor();
- }
- }
-
- private RxRoom() {
- }
-}
diff --git a/room/room-rxjava3/src/main/java/androidx/room/rxjava3/RxRoom.kt b/room/room-rxjava3/src/main/java/androidx/room/rxjava3/RxRoom.kt
new file mode 100644
index 0000000..82ee4b5
--- /dev/null
+++ b/room/room-rxjava3/src/main/java/androidx/room/rxjava3/RxRoom.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("RxRoom")
+
+package androidx.room.rxjava3
+
+import androidx.annotation.RestrictTo
+import androidx.room.InvalidationTracker
+import androidx.room.RoomDatabase
+import androidx.room.coroutines.createFlow
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteConnection
+import io.reactivex.rxjava3.core.BackpressureStrategy
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Flowable
+import io.reactivex.rxjava3.core.FlowableEmitter
+import io.reactivex.rxjava3.core.Maybe
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.ObservableEmitter
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.disposables.Disposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+import java.util.concurrent.Callable
+import java.util.concurrent.Executor
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.rx3.asObservable
+
+/** Marker class used by annotation processor to identify dependency is in the classpath. */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) class Rx3RoomArtifactMarker private constructor()
+
+/** Data dispatched by the publisher created by [createFlowable]. */
+@JvmField val NOTHING: Any = Any()
+
+/** Helper function used by generated code to create a [Flowable] */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun <T : Any> createFlowable(
+ db: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ block: (SQLiteConnection) -> T?
+): Flowable<T> =
+ createObservable(db, inTransaction, tableNames, block).toFlowable(BackpressureStrategy.LATEST)
+
+/** Helper function used by generated code to create a [Observable] */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun <T : Any> createObservable(
+ db: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ block: (SQLiteConnection) -> T?
+): Observable<T> =
+ createFlow(db, inTransaction, tableNames, block)
+ .filterNotNull()
+ .asObservable(db.getQueryContext())
+
+/** Helper function used by generated code to create a [Maybe] */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun <T : Any> createMaybe(
+ db: RoomDatabase,
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> T?
+): Maybe<T> =
+ Maybe.fromCallable(Callable<T> { performBlocking(db, isReadOnly, inTransaction, block) })
+
+/** Helper function used by generated code to create a [Completable] */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun createCompletable(
+ db: RoomDatabase,
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> Unit
+): Completable = Completable.fromCallable { performBlocking(db, isReadOnly, inTransaction, block) }
+
+/** Helper function used by generated code to create a [Single] */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun <T : Any> createSingle(
+ db: RoomDatabase,
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> T?
+): Single<T> =
+ Single.create { emitter ->
+ if (emitter.isDisposed) return@create
+ try {
+ val result = performBlocking(db, isReadOnly, inTransaction, block)
+ if (result != null) {
+ emitter.onSuccess(result)
+ } else {
+ throw EmptyResultSetException("Query returned empty result set.")
+ }
+ } catch (e: EmptyResultSetException) {
+ emitter.tryOnError(e)
+ }
+ }
+
+/**
+ * Creates a [Flowable] that emits at least once and also re-emits whenever one of the observed
+ * tables is updated.
+ *
+ * You can easily chain a database operation to downstream of this [Flowable] to ensure that it
+ * re-runs when database is modified.
+ *
+ * Since database invalidation is batched, multiple changes in the database may results in just 1
+ * emission.
+ *
+ * @param database The database instance
+ * @param tableNames The list of table names that should be observed
+ * @return A [Flowable] which emits [NOTHING] when one of the observed tables is modified (also once
+ * when the invalidation tracker connection is established).
+ */
+fun createFlowable(database: RoomDatabase, vararg tableNames: String): Flowable<Any> {
+ return Flowable.create(
+ { emitter: FlowableEmitter<Any> ->
+ val observer =
+ object : InvalidationTracker.Observer(tableNames) {
+ override fun onInvalidated(tables: Set<String>) {
+ if (!emitter.isCancelled) {
+ emitter.onNext(NOTHING)
+ }
+ }
+ }
+ if (!emitter.isCancelled) {
+ database.invalidationTracker.addObserver(observer)
+ emitter.setDisposable(
+ Disposable.fromAction { database.invalidationTracker.removeObserver(observer) }
+ )
+ }
+
+ // emit once to avoid missing any data and also easy chaining
+ if (!emitter.isCancelled) {
+ emitter.onNext(NOTHING)
+ }
+ },
+ BackpressureStrategy.LATEST
+ )
+}
+
+/**
+ * Helper method used by generated code to bind a Callable such that it will be run in our disk io
+ * thread and will automatically block null values since RxJava3 does not like null.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+@Deprecated("No longer used by generated code.")
+fun <T : Any> createFlowable(
+ database: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ callable: Callable<out T>
+): Flowable<T> {
+ val scheduler = Schedulers.from(getExecutor(database, inTransaction))
+ val maybe = Maybe.fromCallable(callable)
+ return createFlowable(database, *tableNames)
+ .subscribeOn(scheduler)
+ .unsubscribeOn(scheduler)
+ .observeOn(scheduler)
+ .flatMapMaybe { maybe }
+}
+
+/**
+ * Creates a [Observable] that emits at least once and also re-emits whenever one of the observed
+ * tables is updated.
+ *
+ * You can easily chain a database operation to downstream of this [Observable] to ensure that it
+ * re-runs when database is modified.
+ *
+ * Since database invalidation is batched, multiple changes in the database may results in just 1
+ * emission.
+ *
+ * @param database The database instance
+ * @param tableNames The list of table names that should be observed
+ * @return A [Observable] which emits [NOTHING] when one of the observed tables is modified (also
+ * once when the invalidation tracker connection is established).
+ */
+fun createObservable(database: RoomDatabase, vararg tableNames: String): Observable<Any> {
+ return Observable.create { emitter: ObservableEmitter<Any> ->
+ val observer =
+ object : InvalidationTracker.Observer(tableNames) {
+ override fun onInvalidated(tables: Set<String>) {
+ emitter.onNext(NOTHING)
+ }
+ }
+ database.invalidationTracker.addObserver(observer)
+ emitter.setDisposable(
+ Disposable.fromAction { database.invalidationTracker.removeObserver(observer) }
+ )
+
+ // emit once to avoid missing any data and also easy chaining
+ emitter.onNext(NOTHING)
+ }
+}
+
+/**
+ * Helper method used by generated code to bind a Callable such that it will be run in our disk io
+ * thread and will automatically block null values since RxJava3 does not like null.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+@Deprecated("No longer used by generated code.")
+fun <T : Any> createObservable(
+ database: RoomDatabase,
+ inTransaction: Boolean,
+ tableNames: Array<String>,
+ callable: Callable<out T>
+): Observable<T> {
+ val scheduler = Schedulers.from(getExecutor(database, inTransaction))
+ val maybe = Maybe.fromCallable(callable)
+ return createObservable(database, *tableNames)
+ .subscribeOn(scheduler)
+ .unsubscribeOn(scheduler)
+ .observeOn(scheduler)
+ .flatMapMaybe { maybe }
+}
+
+/**
+ * Helper method used by generated code to create a Single from a Callable that will ignore the
+ * EmptyResultSetException if the stream is already disposed.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun <T : Any> createSingle(callable: Callable<out T>): Single<T> {
+ return Single.create { emitter ->
+ try {
+ val result = callable.call()
+ if (result != null) {
+ emitter.onSuccess(result)
+ } else {
+ throw EmptyResultSetException("Query returned empty result set.")
+ }
+ } catch (e: EmptyResultSetException) {
+ emitter.tryOnError(e)
+ }
+ }
+}
+
+private fun getExecutor(database: RoomDatabase, inTransaction: Boolean): Executor {
+ return if (inTransaction) {
+ database.transactionExecutor
+ } else {
+ database.queryExecutor
+ }
+}
diff --git a/room/room-rxjava3/src/test/java/androidx/room/rxjava3/RxRoomTest.kt b/room/room-rxjava3/src/test/java/androidx/room/rxjava3/RxRoomTest.kt
index 256e5c2..27f5606 100644
--- a/room/room-rxjava3/src/test/java/androidx/room/rxjava3/RxRoomTest.kt
+++ b/room/room-rxjava3/src/test/java/androidx/room/rxjava3/RxRoomTest.kt
@@ -23,6 +23,7 @@
import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.observers.TestObserver
import io.reactivex.rxjava3.subscribers.TestSubscriber
+import java.util.concurrent.Callable
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
import org.junit.Before
@@ -62,7 +63,7 @@
@Test
fun basicAddRemove_Flowable() {
- val flowable = RxRoom.createFlowable(mDatabase, "a", "b")
+ val flowable = createFlowable(mDatabase, "a", "b")
verify(mInvalidationTracker, never()).addObserver(any())
var disposable = flowable.subscribe()
verify(mInvalidationTracker).addObserver(any())
@@ -82,7 +83,7 @@
@Test
fun basicAddRemove_Observable() {
- val observable = RxRoom.createObservable(mDatabase, "a", "b")
+ val observable = createObservable(mDatabase, "a", "b")
verify(mInvalidationTracker, never()).addObserver(any())
var disposable = observable.subscribe()
verify(mInvalidationTracker).addObserver(any())
@@ -104,7 +105,7 @@
fun basicNotify_Flowable() {
val tables = arrayOf("a", "b")
val tableSet: Set<String> = HashSet(listOf(*tables))
- val flowable = RxRoom.createFlowable(mDatabase, *tables)
+ val flowable = createFlowable(mDatabase, *tables)
val consumer = CountingConsumer()
val disposable = flowable.subscribe(consumer)
assertThat(mAddedObservers.size).isEqualTo(1)
@@ -123,7 +124,7 @@
fun basicNotify_Observable() {
val tables = arrayOf("a", "b")
val tableSet: Set<String> = HashSet(listOf(*tables))
- val observable = RxRoom.createObservable(mDatabase, *tables)
+ val observable = createObservable(mDatabase, *tables)
val consumer = CountingConsumer()
val disposable = observable.subscribe(consumer)
assertThat(mAddedObservers.size).isEqualTo(1)
@@ -139,12 +140,12 @@
}
@Test
- @Throws(Exception::class)
+ @Suppress("DEPRECATION")
fun internalCallable_Flowable() {
val value = AtomicReference<Any>(null)
val tables = arrayOf("a", "b")
val tableSet: Set<String> = HashSet(listOf(*tables))
- val flowable = RxRoom.createFlowable(mDatabase, false, tables) { value.get() }
+ val flowable = createFlowable(mDatabase, false, tables, Callable { value.get() })
val consumer = CountingConsumer()
val disposable = flowable.subscribe(consumer)
drain()
@@ -169,12 +170,12 @@
}
@Test
- @Throws(Exception::class)
+ @Suppress("DEPRECATION")
fun internalCallable_Observable() {
val value = AtomicReference<Any>(null)
val tables = arrayOf("a", "b")
val tableSet: Set<String> = HashSet(listOf(*tables))
- val flowable = RxRoom.createObservable(mDatabase, false, tables) { value.get() }
+ val flowable = createObservable(mDatabase, false, tables, Callable { value.get() })
val consumer = CountingConsumer()
val disposable = flowable.subscribe(consumer)
drain()
@@ -199,12 +200,15 @@
}
@Test
- @Throws(Exception::class)
+ @Suppress("DEPRECATION")
fun exception_Flowable() {
val flowable =
- RxRoom.createFlowable<String>(mDatabase, false, arrayOf("a")) {
- throw Exception("i want exception")
- }
+ createFlowable<String>(
+ mDatabase,
+ false,
+ arrayOf("a"),
+ Callable { throw Exception("i want exception") }
+ )
val subscriber = TestSubscriber<String>()
flowable.subscribe(subscriber)
drain()
@@ -212,12 +216,15 @@
}
@Test
- @Throws(Exception::class)
+ @Suppress("DEPRECATION")
fun exception_Observable() {
val flowable =
- RxRoom.createObservable<String>(mDatabase, false, arrayOf("a")) {
- throw Exception("i want exception")
- }
+ createObservable<String>(
+ mDatabase,
+ false,
+ arrayOf("a"),
+ Callable { throw Exception("i want exception") }
+ )
val observer = TestObserver<String>()
flowable.subscribe(observer)
drain()
diff --git a/sqlite/sqlite-bundled/build.gradle b/sqlite/sqlite-bundled/build.gradle
index 6c4caeb..3bc7daf 100644
--- a/sqlite/sqlite-bundled/build.gradle
+++ b/sqlite/sqlite-bundled/build.gradle
@@ -249,5 +249,4 @@
inceptionYear = "2023"
description = "The implementation of SQLite library using the bundled SQLite."
metalavaK2UastEnabled = false
- legacyDisableKotlinStrictApiMode = true
}
diff --git a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.kt b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.kt
index 6adf9ca..30bd8d1 100644
--- a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.kt
+++ b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.kt
@@ -23,39 +23,39 @@
import kotlin.jvm.JvmName
/** Opens the database in read-only mode. */
-const val SQLITE_OPEN_READONLY = 0x00000001
+public const val SQLITE_OPEN_READONLY: Int = 0x00000001
/** Opens the database for reading and writing. */
-const val SQLITE_OPEN_READWRITE = 0x00000002
+public const val SQLITE_OPEN_READWRITE: Int = 0x00000002
/** Create the database if it does not already exist. */
-const val SQLITE_OPEN_CREATE = 0x00000004
+public const val SQLITE_OPEN_CREATE: Int = 0x00000004
/** Interpret the filename as a URI. */
-const val SQLITE_OPEN_URI = 0x00000040
+public const val SQLITE_OPEN_URI: Int = 0x00000040
/** Opens the database as a in-memory database. */
-const val SQLITE_OPEN_MEMORY = 0x00000080
+public const val SQLITE_OPEN_MEMORY: Int = 0x00000080
/**
* The database connection will use the "multi-thread" threading mode.
*
* See also [SQLite In Multi-Threaded Applications](https://www.sqlite.org/threadsafe.html)
*/
-const val SQLITE_OPEN_NOMUTEX = 0x00008000
+public const val SQLITE_OPEN_NOMUTEX: Int = 0x00008000
/**
* The database connection will use the "serialized" threading mode.
*
* See also [SQLite In Multi-Threaded Applications](https://www.sqlite.org/threadsafe.html)
*/
-const val SQLITE_OPEN_FULLMUTEX = 0x00010000
+public const val SQLITE_OPEN_FULLMUTEX: Int = 0x00010000
/** The filename is not allowed to contain a symbolic link. */
-const val SQLITE_OPEN_NOFOLLOW = 0x01000000
+public const val SQLITE_OPEN_NOFOLLOW: Int = 0x01000000
/** The database connection will use extended result codes. */
-const val SQLITE_OPEN_EXRESCODE = 0x02000000
+public const val SQLITE_OPEN_EXRESCODE: Int = 0x02000000
/** The flags constant that can be used with [BundledSQLiteDriver.open]. */
@IntDef(
@@ -75,4 +75,4 @@
)
@Retention(AnnotationRetention.SOURCE)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-expect annotation class OpenFlag()
+public expect annotation class OpenFlag()
diff --git a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.kt b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.kt
index 49b5731..0eec41a 100644
--- a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.kt
+++ b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.kt
@@ -20,4 +20,5 @@
import androidx.sqlite.SQLiteConnection
// Restricted instead of internal due to KT-37316
-@RestrictTo(RestrictTo.Scope.LIBRARY) expect class BundledSQLiteConnection : SQLiteConnection
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public expect class BundledSQLiteConnection : SQLiteConnection
diff --git a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.kt b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.kt
index b55c476..66ee02b 100644
--- a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.kt
+++ b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.kt
@@ -24,14 +24,14 @@
* A [SQLiteDriver] that uses a bundled version of SQLite included as a native component of this
* library.
*/
-expect class BundledSQLiteDriver() : SQLiteDriver {
+public expect class BundledSQLiteDriver() : SQLiteDriver {
/**
* The thread safe mode SQLite was compiled with.
*
* See also [SQLite In Multi-Threaded Applications](https://www.sqlite.org/threadsafe.html)
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) val threadingMode: Int
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public val threadingMode: Int
/**
* Opens a new database connection.
@@ -42,5 +42,5 @@
* @param flags Connection open flags.
* @return the database connection.
*/
- fun open(fileName: String, @OpenFlag flags: Int): SQLiteConnection
+ public fun open(fileName: String, @OpenFlag flags: Int): SQLiteConnection
}
diff --git a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.kt b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.kt
index a48d56e..7b82a28 100644
--- a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.kt
+++ b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.kt
@@ -20,4 +20,4 @@
import androidx.sqlite.SQLiteStatement
// Restricted instead of internal due to KT-37316
-@RestrictTo(RestrictTo.Scope.LIBRARY) expect class BundledSQLiteStatement : SQLiteStatement
+@RestrictTo(RestrictTo.Scope.LIBRARY) public expect class BundledSQLiteStatement : SQLiteStatement
diff --git a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.jvmAndroid.kt b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.jvmAndroid.kt
index 1717770..d283118 100644
--- a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.jvmAndroid.kt
+++ b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.jvmAndroid.kt
@@ -36,7 +36,7 @@
)
@Retention(AnnotationRetention.SOURCE)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-actual annotation class OpenFlag
+public actual annotation class OpenFlag
internal object ResultCode {
const val SQLITE_MISUSE = 21
diff --git a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.jvmAndroid.kt b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.jvmAndroid.kt
index 65be6da..3d34667 100644
--- a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.jvmAndroid.kt
+++ b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.jvmAndroid.kt
@@ -24,7 +24,8 @@
import androidx.sqlite.throwSQLiteException
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-actual class BundledSQLiteConnection(private val connectionPointer: Long) : SQLiteConnection {
+public actual class BundledSQLiteConnection(private val connectionPointer: Long) :
+ SQLiteConnection {
@OptIn(ExperimentalStdlibApi::class) @Volatile private var isClosed = false
diff --git a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.jvmAndroid.kt b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.jvmAndroid.kt
index de1e6eb..a38a069 100644
--- a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.jvmAndroid.kt
+++ b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.jvmAndroid.kt
@@ -27,7 +27,7 @@
*/
// TODO(b/313895287): Explore usability of @FastNative and @CriticalNative for the external
// functions.
-actual class BundledSQLiteDriver : SQLiteDriver {
+public actual class BundledSQLiteDriver : SQLiteDriver {
/**
* The thread safe mode SQLite was compiled with.
@@ -35,7 +35,7 @@
* See also [SQLite In Multi-Threaded Applications](https://www.sqlite.org/threadsafe.html)
*/
@get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- actual val threadingMode: Int
+ public actual val threadingMode: Int
get() = nativeThreadSafeMode()
override fun open(fileName: String): SQLiteConnection {
@@ -51,7 +51,7 @@
* @param flags Connection open flags.
* @return the database connection.
*/
- actual fun open(fileName: String, @OpenFlag flags: Int): SQLiteConnection {
+ public actual fun open(fileName: String, @OpenFlag flags: Int): SQLiteConnection {
val address = nativeOpen(fileName, flags)
return BundledSQLiteConnection(address)
}
diff --git a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.jvmAndroid.kt b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.jvmAndroid.kt
index f33c4ab..f2577c5 100644
--- a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.jvmAndroid.kt
+++ b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.jvmAndroid.kt
@@ -23,7 +23,7 @@
import androidx.sqlite.throwSQLiteException
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-actual class BundledSQLiteStatement(
+public actual class BundledSQLiteStatement(
private val connectionPointer: Long,
private val statementPointer: Long
) : SQLiteStatement {
@@ -118,7 +118,7 @@
}
}
- companion object {
+ private companion object {
private const val COLUMN_TYPE_INTEGER = 1
private const val COLUMN_TYPE_FLOAT = 2
private const val COLUMN_TYPE_TEXT = 3
diff --git a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.nativeCommon.kt b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.nativeCommon.kt
index d3d1d5d..2a65818 100644
--- a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.nativeCommon.kt
+++ b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.nativeCommon.kt
@@ -16,4 +16,4 @@
package androidx.sqlite.driver.bundled
-actual typealias OpenFlag = androidx.sqlite.driver.OpenFlag
+public actual typealias OpenFlag = androidx.sqlite.driver.OpenFlag
diff --git a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.native.kt b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.native.kt
index d466e1a..a368590 100644
--- a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.native.kt
+++ b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.native.kt
@@ -20,4 +20,4 @@
import androidx.annotation.RestrictTo
-actual typealias BundledSQLiteConnection = androidx.sqlite.driver.NativeSQLiteConnection
+public actual typealias BundledSQLiteConnection = androidx.sqlite.driver.NativeSQLiteConnection
diff --git a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.native.kt b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.native.kt
index c39d148..4d2be0d 100644
--- a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.native.kt
+++ b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.native.kt
@@ -22,4 +22,4 @@
* A [SQLiteDriver] that uses a bundled version of SQLite included as a native component of this
* library.
*/
-actual typealias BundledSQLiteDriver = androidx.sqlite.driver.NativeSQLiteDriver
+public actual typealias BundledSQLiteDriver = androidx.sqlite.driver.NativeSQLiteDriver
diff --git a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.native.kt b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.native.kt
index 92e09eb..7afcd68 100644
--- a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.native.kt
+++ b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.native.kt
@@ -20,4 +20,4 @@
import androidx.annotation.RestrictTo
-actual typealias BundledSQLiteStatement = androidx.sqlite.driver.NativeSQLiteStatement
+public actual typealias BundledSQLiteStatement = androidx.sqlite.driver.NativeSQLiteStatement
diff --git a/sqlite/sqlite-framework/build.gradle b/sqlite/sqlite-framework/build.gradle
index 7ad2380..411b332f 100644
--- a/sqlite/sqlite-framework/build.gradle
+++ b/sqlite/sqlite-framework/build.gradle
@@ -146,7 +146,6 @@
type = LibraryType.PUBLISHED_LIBRARY
inceptionYear = "2017"
description = "The implementation of SQLite library using the framework code."
- legacyDisableKotlinStrictApiMode = true
}
android {
diff --git a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.android.kt b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.android.kt
index f4a55ee..fc1617e 100644
--- a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.android.kt
+++ b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.android.kt
@@ -20,7 +20,7 @@
/**
* Implements [SupportSQLiteOpenHelper.Factory] using the SQLite implementation in the framework.
*/
-class FrameworkSQLiteOpenHelperFactory : SupportSQLiteOpenHelper.Factory {
+public class FrameworkSQLiteOpenHelperFactory : SupportSQLiteOpenHelper.Factory {
override fun create(
configuration: SupportSQLiteOpenHelper.Configuration
): SupportSQLiteOpenHelper {
diff --git a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteConnection.android.kt b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteConnection.android.kt
index 375a279..75ae53e 100644
--- a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteConnection.android.kt
+++ b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteConnection.android.kt
@@ -24,7 +24,7 @@
import androidx.sqlite.throwSQLiteException
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-class AndroidSQLiteConnection(val db: SQLiteDatabase) : SQLiteConnection {
+public class AndroidSQLiteConnection(public val db: SQLiteDatabase) : SQLiteConnection {
override fun prepare(sql: String): SQLiteStatement {
if (db.isOpen) {
return AndroidSQLiteStatement.create(db, sql)
diff --git a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteDriver.android.kt b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteDriver.android.kt
index 245be46..9db9d96 100644
--- a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteDriver.android.kt
+++ b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteDriver.android.kt
@@ -23,7 +23,7 @@
/**
* A [SQLiteDriver] implemented by [android.database] and that uses the Android's SDK SQLite APIs.
*/
-class AndroidSQLiteDriver : SQLiteDriver {
+public class AndroidSQLiteDriver : SQLiteDriver {
override fun open(fileName: String): SQLiteConnection {
val database = SQLiteDatabase.openOrCreateDatabase(fileName, null)
return AndroidSQLiteConnection(database)
diff --git a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/util/ProcessLock.android.kt b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/util/ProcessLock.android.kt
index ebb9046..706903c 100644
--- a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/util/ProcessLock.android.kt
+++ b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/util/ProcessLock.android.kt
@@ -47,7 +47,7 @@
* can be overridden via the [lock] method.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class ProcessLock(name: String, lockDir: File?, private val processLock: Boolean) {
+public class ProcessLock(name: String, lockDir: File?, private val processLock: Boolean) {
private val lockFile: File? = lockDir?.let { File(it, "$name.lck") }
private val threadLock: Lock = getThreadLock(name)
private var lockChannel: FileChannel? = null
@@ -57,7 +57,7 @@
*
* @param [processLock] whether to use file for process level locking or not.
*/
- fun lock(processLock: Boolean = this.processLock) {
+ public fun lock(processLock: Boolean = this.processLock) {
threadLock.lock()
if (processLock) {
try {
@@ -76,14 +76,14 @@
}
/** Releases the lock. */
- fun unlock() {
+ public fun unlock() {
try {
lockChannel?.close()
} catch (ignored: IOException) {}
threadLock.unlock()
}
- companion object {
+ private companion object {
private const val TAG = "SupportSQLiteLock"
// in-process lock map
private val threadLocksMap: MutableMap<String, Lock> = HashMap()
diff --git a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLite.kt b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLite.kt
index 0e060fa..2534593 100644
--- a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLite.kt
+++ b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLite.kt
@@ -52,7 +52,7 @@
)
@Retention(AnnotationRetention.SOURCE)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-annotation class OpenFlag
+public annotation class OpenFlag
internal fun CPointer<sqlite3>.getErrorMsg(): String? {
return sqlite3_errmsg16(this)?.reinterpret<UShortVar>()?.toKStringFromUtf16()
diff --git a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteConnection.kt b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteConnection.kt
index c3fce96..6ff1dbb 100644
--- a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteConnection.kt
+++ b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteConnection.kt
@@ -35,7 +35,7 @@
import sqlite3.sqlite3_prepare16_v2
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // For actual typealias in unbundled
-class NativeSQLiteConnection(private val dbPointer: CPointer<sqlite3>) : SQLiteConnection {
+public class NativeSQLiteConnection(private val dbPointer: CPointer<sqlite3>) : SQLiteConnection {
@OptIn(ExperimentalStdlibApi::class) @Volatile private var isClosed = false
diff --git a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteDriver.kt b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteDriver.kt
index 5040a1e..ebfe6d8 100644
--- a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteDriver.kt
+++ b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteDriver.kt
@@ -36,7 +36,7 @@
*
* Usage of this driver expects that `libsqlite` can be found in the shared library path.
*/
-class NativeSQLiteDriver : SQLiteDriver {
+public class NativeSQLiteDriver : SQLiteDriver {
/**
* The thread safe mode SQLite was compiled with.
@@ -44,7 +44,7 @@
* See also [SQLite In Multi-Threaded Applications](https://www.sqlite.org/threadsafe.html)
*/
@get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- val threadingMode: Int
+ public val threadingMode: Int
get() = sqlite3_threadsafe()
override fun open(fileName: String): SQLiteConnection {
@@ -60,7 +60,7 @@
* @param flags Connection open flags.
* @return the database connection.
*/
- fun open(fileName: String, @OpenFlag flags: Int): SQLiteConnection = memScoped {
+ public fun open(fileName: String, @OpenFlag flags: Int): SQLiteConnection = memScoped {
val dbPointer = allocPointerTo<sqlite3>()
val resultCode =
sqlite3_open_v2(filename = fileName, ppDb = dbPointer.ptr, flags = flags, zVfs = null)
diff --git a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt
index 1995de0..dfd2829 100644
--- a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt
+++ b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt
@@ -57,7 +57,7 @@
import sqlite3.sqlite3_step
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // For actual typealias in unbundled
-class NativeSQLiteStatement(
+public class NativeSQLiteStatement(
private val dbPointer: CPointer<sqlite3>,
private val stmtPointer: CPointer<sqlite3_stmt>
) : SQLiteStatement {
diff --git a/sqlite/sqlite-ktx/build.gradle b/sqlite/sqlite-ktx/build.gradle
index b03f306..90d69c5 100644
--- a/sqlite/sqlite-ktx/build.gradle
+++ b/sqlite/sqlite-ktx/build.gradle
@@ -42,7 +42,6 @@
type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
inceptionYear = "2018"
description = "Kotlin extensions for DB"
- legacyDisableKotlinStrictApiMode = true
}
android {
diff --git a/sqlite/sqlite-ktx/src/main/java/androidx/sqlite/db/SupportSQLiteDatabaseExt.kt b/sqlite/sqlite-ktx/src/main/java/androidx/sqlite/db/SupportSQLiteDatabaseExt.kt
index e637a991..51d244f 100644
--- a/sqlite/sqlite-ktx/src/main/java/androidx/sqlite/db/SupportSQLiteDatabaseExt.kt
+++ b/sqlite/sqlite-ktx/src/main/java/androidx/sqlite/db/SupportSQLiteDatabaseExt.kt
@@ -23,7 +23,7 @@
* @param exclusive Run in `EXCLUSIVE` mode when true, `IMMEDIATE` mode otherwise.
* @param body Lambda to be run in the transaction.
*/
-inline fun <T> SupportSQLiteDatabase.transaction(
+public inline fun <T> SupportSQLiteDatabase.transaction(
exclusive: Boolean = true,
body: SupportSQLiteDatabase.() -> T
): T {
diff --git a/sqlite/sqlite/build.gradle b/sqlite/sqlite/build.gradle
index 59c4baf..3c4a438 100644
--- a/sqlite/sqlite/build.gradle
+++ b/sqlite/sqlite/build.gradle
@@ -90,5 +90,4 @@
type = LibraryType.PUBLISHED_LIBRARY
inceptionYear = "2017"
description = "SQLite API"
- legacyDisableKotlinStrictApiMode = true
}
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/SQLiteException.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/SQLiteException.android.kt
index febb4af..6401e1f 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/SQLiteException.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/SQLiteException.android.kt
@@ -15,4 +15,4 @@
*/
package androidx.sqlite
-actual typealias SQLiteException = android.database.SQLException
+public actual typealias SQLiteException = android.database.SQLException
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SimpleSQLiteQuery.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SimpleSQLiteQuery.android.kt
index bbe86d9..1939a45 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SimpleSQLiteQuery.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SimpleSQLiteQuery.android.kt
@@ -24,7 +24,7 @@
* @constructor Creates an SQL query with the sql string and the bind arguments.
*/
@Suppress("AcronymName") // SQL is a known term and should remain capitalized
-class SimpleSQLiteQuery(
+public class SimpleSQLiteQuery(
private val query: String,
@Suppress("ArrayReturn") // Due to legacy API
private val bindArgs: Array<out Any?>?
@@ -35,7 +35,7 @@
*
* @param query The SQL query to execute. Cannot include bind parameters.
*/
- constructor(query: String) : this(query, null)
+ public constructor(query: String) : this(query, null)
override val sql: String
get() = this.query
@@ -53,7 +53,7 @@
override val argCount: Int
get() = bindArgs?.size ?: 0
- companion object {
+ public companion object {
/**
* Binds the given arguments into the given sqlite statement.
*
@@ -61,7 +61,7 @@
* @param [bindArgs] The list of bind arguments
*/
@JvmStatic
- fun bind(
+ public fun bind(
@Suppress("AcronymName") // SQL is a known term and should remain capitalized
statement: SupportSQLiteProgram,
@Suppress("ArrayReturn") // Due to legacy API
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteCompat.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteCompat.android.kt
index 856d8ba..8e56dc1 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteCompat.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteCompat.android.kt
@@ -26,11 +26,11 @@
/** Helper for accessing features in [SupportSQLiteOpenHelper]. */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class SupportSQLiteCompat private constructor() {
+public class SupportSQLiteCompat private constructor() {
/** Helper for accessing functions that require SDK version 21 and higher. */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@RequiresApi(21)
- object Api21Impl {
+ public object Api21Impl {
/**
* Returns the absolute path to the directory on the filesystem.
*
@@ -39,7 +39,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@JvmStatic
- fun getNoBackupFilesDir(context: Context): File {
+ public fun getNoBackupFilesDir(context: Context): File {
return context.noBackupFilesDir
}
}
@@ -47,7 +47,7 @@
/** Helper for accessing functions that require SDK version 23 and higher. */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@RequiresApi(23)
- object Api23Impl {
+ public object Api23Impl {
/**
* Sets a [Bundle] that will be returned by [Cursor.getExtras].
*
@@ -55,7 +55,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@JvmStatic
- fun setExtras(cursor: Cursor, extras: Bundle) {
+ public fun setExtras(cursor: Cursor, extras: Bundle) {
cursor.extras = extras
}
}
@@ -63,7 +63,7 @@
/** Helper for accessing functions that require SDK version 29 and higher. */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@RequiresApi(29)
- object Api29Impl {
+ public object Api29Impl {
/**
* Similar to [Cursor.setNotificationUri], except this version allows to watch multiple
* content URIs for changes.
@@ -74,7 +74,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@JvmStatic
- fun setNotificationUris(cursor: Cursor, cr: ContentResolver, uris: List<Uri?>) {
+ public fun setNotificationUris(cursor: Cursor, cr: ContentResolver, uris: List<Uri?>) {
cursor.setNotificationUris(cr, uris)
}
@@ -88,7 +88,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@JvmStatic
- fun getNotificationUris(cursor: Cursor): List<Uri> {
+ public fun getNotificationUris(cursor: Cursor): List<Uri> {
return cursor.notificationUris!!
}
}
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteDatabase.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteDatabase.android.kt
index 4f4e98c..5c6d9dd 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteDatabase.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteDatabase.android.kt
@@ -30,14 +30,14 @@
* versions. It mimics the behavior of [android.database.sqlite.SQLiteDatabase]
*/
@Suppress("AcronymName") // SQL is a known term and should remain capitalized
-interface SupportSQLiteDatabase : Closeable {
+public interface SupportSQLiteDatabase : Closeable {
/**
* Compiles the given SQL statement.
*
* @param sql The sql query.
* @return Compiled statement.
*/
- fun compileStatement(sql: String): SupportSQLiteStatement
+ public fun compileStatement(sql: String): SupportSQLiteStatement
/**
* Begins a transaction in EXCLUSIVE mode.
@@ -58,7 +58,7 @@
* }
* ```
*/
- fun beginTransaction()
+ public fun beginTransaction()
/**
* Begins a transaction in IMMEDIATE mode. Transactions can be nested. When the outer
@@ -78,7 +78,7 @@
* }
* ```
*/
- fun beginTransactionNonExclusive()
+ public fun beginTransactionNonExclusive()
/**
* Begins a transaction in DEFERRED mode, with the android-specific constraint that the
@@ -107,7 +107,7 @@
* If the implementation does not support read-only transactions then the default implementation
* delegates to [beginTransaction].
*/
- fun beginTransactionReadOnly() {
+ public fun beginTransactionReadOnly() {
beginTransaction()
}
@@ -133,7 +133,7 @@
* @param transactionListener listener that should be notified when the transaction begins,
* commits, or is rolled back, either explicitly or by a call to [yieldIfContendedSafely].
*/
- fun beginTransactionWithListener(transactionListener: SQLiteTransactionListener)
+ public fun beginTransactionWithListener(transactionListener: SQLiteTransactionListener)
/**
* Begins a transaction in IMMEDIATE mode. Transactions can be nested. When the outer
@@ -156,7 +156,9 @@
* @param transactionListener listener that should be notified when the transaction begins,
* commits, or is rolled back, either explicitly or by a call to [yieldIfContendedSafely].
*/
- fun beginTransactionWithListenerNonExclusive(transactionListener: SQLiteTransactionListener)
+ public fun beginTransactionWithListenerNonExclusive(
+ transactionListener: SQLiteTransactionListener
+ )
/**
* Begins a transaction in read-only mode with a {@link SQLiteTransactionListener} listener. The
@@ -182,7 +184,9 @@
* delegates to [beginTransactionWithListener].
*/
@Suppress("ExecutorRegistration")
- fun beginTransactionWithListenerReadOnly(transactionListener: SQLiteTransactionListener) {
+ public fun beginTransactionWithListenerReadOnly(
+ transactionListener: SQLiteTransactionListener
+ ) {
beginTransactionWithListener(transactionListener)
}
@@ -190,7 +194,7 @@
* End a transaction. See beginTransaction for notes about how to use this and when transactions
* are committed and rolled back.
*/
- fun endTransaction()
+ public fun endTransaction()
/**
* Marks the current transaction as successful. Do not do any more database work between calling
@@ -201,14 +205,14 @@
* @throws IllegalStateException if the current thread is not in a transaction or the
* transaction is already marked as successful.
*/
- fun setTransactionSuccessful()
+ public fun setTransactionSuccessful()
/**
* Returns true if the current thread has a transaction pending.
*
* @return True if the current thread is in a transaction.
*/
- fun inTransaction(): Boolean
+ public fun inTransaction(): Boolean
/**
* True if the current thread is holding an active connection to the database.
@@ -218,7 +222,7 @@
* longer a true "database lock" although threads may block if they cannot acquire a database
* connection to perform a particular operation.
*/
- val isDbLockedByCurrentThread: Boolean
+ public val isDbLockedByCurrentThread: Boolean
/**
* Temporarily end the transaction to let other threads run. The transaction is assumed to be
@@ -229,7 +233,7 @@
*
* @return true if the transaction was yielded
*/
- fun yieldIfContendedSafely(): Boolean
+ public fun yieldIfContendedSafely(): Boolean
/**
* Temporarily end the transaction to let other threads run. The transaction is assumed to be
@@ -243,11 +247,11 @@
* more progress than they would if we started the transaction immediately.
* @return true if the transaction was yielded
*/
- fun yieldIfContendedSafely(sleepAfterYieldDelayMillis: Long): Boolean
+ public fun yieldIfContendedSafely(sleepAfterYieldDelayMillis: Long): Boolean
/** Is true if [execPerConnectionSQL] is supported by the implementation. */
@get:Suppress("AcronymName") // To keep consistency with framework method name.
- val isExecPerConnectionSQLSupported: Boolean
+ public val isExecPerConnectionSQLSupported: Boolean
get() = false
/**
@@ -272,15 +276,18 @@
* supported use [isExecPerConnectionSQLSupported]
*/
@Suppress("AcronymName") // To keep consistency with framework method name.
- fun execPerConnectionSQL(sql: String, @SuppressLint("ArrayReturn") bindArgs: Array<out Any?>?) {
+ public fun execPerConnectionSQL(
+ sql: String,
+ @SuppressLint("ArrayReturn") bindArgs: Array<out Any?>?
+ ) {
throw UnsupportedOperationException()
}
/** The database version. */
- var version: Int
+ public var version: Int
/** The maximum size the database may grow to. */
- val maximumSize: Long
+ public val maximumSize: Long
/**
* Sets the maximum size the database will grow to. The maximum size cannot be set below the
@@ -289,7 +296,7 @@
* @param numBytes the maximum database size, in bytes
* @return the new maximum database size
*/
- fun setMaximumSize(numBytes: Long): Long
+ public fun setMaximumSize(numBytes: Long): Long
/**
* The current database page size, in bytes.
@@ -297,7 +304,7 @@
* The page size must be a power of two. This method does not work if any data has been written
* to the database file, and must be called right after the database has been created.
*/
- var pageSize: Long
+ public var pageSize: Long
/**
* Runs the given query on the database. If you would like to have typed bind arguments, use
@@ -308,7 +315,7 @@
* @return A [Cursor] object, which is positioned before the first entry. Note that [Cursor]s
* are not synchronized, see the documentation for more details.
*/
- fun query(query: String): Cursor
+ public fun query(query: String): Cursor
/**
* Runs the given query on the database. If you would like to have bind arguments, use [query].
@@ -319,7 +326,7 @@
* @return A [Cursor] object, which is positioned before the first entry. Note that [Cursor]s
* are not synchronized, see the documentation for more details.
*/
- fun query(query: String, bindArgs: Array<out Any?>): Cursor
+ public fun query(query: String, bindArgs: Array<out Any?>): Cursor
/**
* Runs the given query on the database.
@@ -331,7 +338,7 @@
* @return A [Cursor] object, which is positioned before the first entry. Note that [Cursor]s
* are not synchronized, see the documentation for more details.
*/
- fun query(query: SupportSQLiteQuery): Cursor
+ public fun query(query: SupportSQLiteQuery): Cursor
/**
* Runs the given query on the database.
@@ -346,7 +353,7 @@
* @return A [Cursor] object, which is positioned before the first entry. Note that [Cursor]s
* are not synchronized, see the documentation for more details.
*/
- fun query(query: SupportSQLiteQuery, cancellationSignal: CancellationSignal?): Cursor
+ public fun query(query: SupportSQLiteQuery, cancellationSignal: CancellationSignal?): Cursor
/**
* Convenience method for inserting a row into the database.
@@ -365,7 +372,7 @@
* @throws SQLException If the insert fails
*/
@Throws(SQLException::class)
- fun insert(table: String, conflictAlgorithm: Int, values: ContentValues): Long
+ public fun insert(table: String, conflictAlgorithm: Int, values: ContentValues): Long
/**
* Convenience method for deleting rows in the database.
@@ -378,7 +385,7 @@
* @return the number of rows affected if a whereClause is passed in, 0 otherwise. To remove all
* rows and get a count pass "1" as the whereClause.
*/
- fun delete(table: String, whereClause: String?, whereArgs: Array<out Any?>?): Int
+ public fun delete(table: String, whereClause: String?, whereArgs: Array<out Any?>?): Int
/**
* Convenience method for updating rows in the database.
@@ -399,7 +406,7 @@
* from whereArgs. The values will be bound as Strings.
* @return the number of rows affected
*/
- fun update(
+ public fun update(
table: String,
conflictAlgorithm: Int,
values: ContentValues,
@@ -420,7 +427,7 @@
*/
@Suppress("AcronymName") // SQL is a known term and should remain capitalized
@Throws(SQLException::class)
- fun execSQL(sql: String)
+ public fun execSQL(sql: String)
/**
* Execute a single SQL statement that does not return any data.
@@ -436,13 +443,13 @@
*/
@Suppress("AcronymName") // SQL is a known term and should remain capitalized
@Throws(SQLException::class)
- fun execSQL(sql: String, bindArgs: Array<out Any?>)
+ public fun execSQL(sql: String, bindArgs: Array<out Any?>)
/** Is true if the database is opened as read only. */
- val isReadOnly: Boolean
+ public val isReadOnly: Boolean
/** Is true if the database is currently open. */
- val isOpen: Boolean
+ public val isOpen: Boolean
/**
* Returns true if the new version code is greater than the current database version.
@@ -450,10 +457,10 @@
* @param newVersion The new version code.
* @return True if the new version code is greater than the current database version.
*/
- fun needUpgrade(newVersion: Int): Boolean
+ public fun needUpgrade(newVersion: Int): Boolean
/** The path to the database file. */
- val path: String?
+ public val path: String?
/**
* Sets the locale for this database. Does nothing if this database has the
@@ -465,7 +472,7 @@
* there is no collator available for the locale you requested. In this case the database
* remains unchanged.
*/
- fun setLocale(locale: Locale)
+ public fun setLocale(locale: Locale)
/**
* Sets the maximum size of the prepared-statement cache for this database. (size of the cache =
@@ -482,7 +489,7 @@
* @throws IllegalStateException if input cacheSize is over the max.
* [android.database.sqlite.SQLiteDatabase.MAX_SQL_CACHE_SIZE].
*/
- fun setMaxSqlCacheSize(cacheSize: Int)
+ public fun setMaxSqlCacheSize(cacheSize: Int)
/**
* Sets whether foreign key constraints are enabled for the database.
@@ -509,7 +516,7 @@
* @throws IllegalStateException if the are transactions is in progress when this method is
* called.
*/
- fun setForeignKeyConstraintsEnabled(enabled: Boolean)
+ public fun setForeignKeyConstraintsEnabled(enabled: Boolean)
/**
* This method enables parallel execution of queries from multiple threads on the same database.
@@ -573,7 +580,7 @@
* @throws IllegalStateException if there are transactions in progress at the time this method
* is called. WAL mode can only be changed when there are no transactions in progress.
*/
- fun enableWriteAheadLogging(): Boolean
+ public fun enableWriteAheadLogging(): Boolean
/**
* This method disables the features enabled by [enableWriteAheadLogging].
@@ -581,20 +588,20 @@
* @throws IllegalStateException if there are transactions in progress at the time this method
* is called. WAL mode can only be changed when there are no transactions in progress.
*/
- fun disableWriteAheadLogging()
+ public fun disableWriteAheadLogging()
/** Is true if write-ahead logging has been enabled for this database. */
- val isWriteAheadLoggingEnabled: Boolean
+ public val isWriteAheadLoggingEnabled: Boolean
/**
* The list of full path names of all attached databases including the main database by
* executing 'pragma database_list' on the database.
*/
- @get:Suppress("NullableCollection") val attachedDbs: List<Pair<String, String>>?
+ @get:Suppress("NullableCollection") public val attachedDbs: List<Pair<String, String>>?
/**
* Is true if the given database (and all its attached databases) pass integrity_check, false
* otherwise.
*/
- val isDatabaseIntegrityOk: Boolean
+ public val isDatabaseIntegrityOk: Boolean
}
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteOpenHelper.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteOpenHelper.android.kt
index 90aee80..2be3a94 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteOpenHelper.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteOpenHelper.android.kt
@@ -32,12 +32,12 @@
* create this and [Callback] to implement the methods that should be overridden.
*/
@Suppress("AcronymName") // SQL is a known term and should remain capitalized
-interface SupportSQLiteOpenHelper : Closeable {
+public interface SupportSQLiteOpenHelper : Closeable {
/**
* Return the name of the SQLite database being opened, as given to the constructor. `null`
* indicates an in-memory database.
*/
- val databaseName: String?
+ public val databaseName: String?
/**
* Enables or disables the use of write-ahead logging for the database.
@@ -49,7 +49,7 @@
*
* @param enabled True if write-ahead logging should be enabled, false if it should be disabled.
*/
- fun setWriteAheadLoggingEnabled(enabled: Boolean)
+ public fun setWriteAheadLoggingEnabled(enabled: Boolean)
/**
* Create and/or open a database that will be used for reading and writing. The first time this
@@ -67,7 +67,7 @@
* @return a read/write database object valid until [close] is called
* @throws SQLiteException if the database cannot be opened for writing
*/
- val writableDatabase: SupportSQLiteDatabase
+ public val writableDatabase: SupportSQLiteDatabase
/**
* Create and/or open a database. This will be the same object returned by [writableDatabase]
@@ -82,7 +82,7 @@
* @return a database object valid until [writableDatabase] or [close] is called.
* @throws SQLiteException if the database cannot be opened
*/
- val readableDatabase: SupportSQLiteDatabase
+ public val readableDatabase: SupportSQLiteDatabase
/** Close any open database object. */
override fun close()
@@ -93,13 +93,13 @@
* Handles various lifecycle events for the SQLite connection, similar to
* [room-runtime.SQLiteOpenHelper].
*/
- abstract class Callback(
+ public abstract class Callback(
/**
* Version number of the database (starting at 1); if the database is older,
* [Callback.onUpgrade] will be used to upgrade the database; if the database is newer,
* [Callback.onDowngrade] will be used to downgrade the database.
*/
- @JvmField val version: Int
+ @JvmField public val version: Int
) {
/**
* Called when the database connection is being configured, to enable features such as
@@ -117,7 +117,7 @@
*
* @param db The database.
*/
- open fun onConfigure(db: SupportSQLiteDatabase) {}
+ public open fun onConfigure(db: SupportSQLiteDatabase) {}
/**
* Called when the database is created for the first time. This is where the creation of
@@ -125,7 +125,7 @@
*
* @param db The database.
*/
- abstract fun onCreate(db: SupportSQLiteDatabase)
+ public abstract fun onCreate(db: SupportSQLiteDatabase)
/**
* Called when the database needs to be upgraded. The implementation should use this method
@@ -145,7 +145,7 @@
* @param oldVersion The old database version.
* @param newVersion The new database version.
*/
- abstract fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int)
+ public abstract fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int)
/**
* Called when the database needs to be downgraded. This is strictly similar to [onUpgrade]
@@ -160,7 +160,7 @@
* @param oldVersion The old database version.
* @param newVersion The new database version.
*/
- open fun onDowngrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
+ public open fun onDowngrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
throw SQLiteException(
"Can't downgrade database from version $oldVersion to $newVersion"
)
@@ -177,7 +177,7 @@
*
* @param db The database.
*/
- open fun onOpen(db: SupportSQLiteDatabase) {}
+ public open fun onOpen(db: SupportSQLiteDatabase) {}
/**
* The method invoked when database corruption is detected. Default implementation will
@@ -186,7 +186,7 @@
* @param db the [SupportSQLiteDatabase] object representing the database on which
* corruption is detected.
*/
- open fun onCorruption(db: SupportSQLiteDatabase) {
+ public open fun onCorruption(db: SupportSQLiteDatabase) {
// the following implementation is taken from {@link DefaultDatabaseErrorHandler}.
Log.e(TAG, "Corruption reported by sqlite on database: $db.path")
// is the corruption detected even before database could be 'opened'?
@@ -245,26 +245,26 @@
}
/** The configuration to create an SQLite open helper object using [Factory]. */
- class Configuration
+ public class Configuration
@Suppress("ExecutorRegistration") // For backwards compatibility
constructor(
/** Context to use to open or create the database. */
- @JvmField val context: Context,
+ @JvmField public val context: Context,
/** Name of the database file, or null for an in-memory database. */
- @JvmField val name: String?,
+ @JvmField public val name: String?,
/** The callback class to handle creation, upgrade and downgrade. */
- @JvmField val callback: Callback,
+ @JvmField public val callback: Callback,
/** If `true` the database will be stored in the no-backup directory. */
- @JvmField @Suppress("ListenerLast") val useNoBackupDirectory: Boolean = false,
+ @JvmField @Suppress("ListenerLast") public val useNoBackupDirectory: Boolean = false,
/**
* If `true` the database will be delete and its data loss in the case that it cannot be
* opened.
*/
- @JvmField @Suppress("ListenerLast") val allowDataLossOnRecovery: Boolean = false
+ @JvmField @Suppress("ListenerLast") public val allowDataLossOnRecovery: Boolean = false
) {
/** Builder class for [Configuration]. */
- open class Builder internal constructor(context: Context) {
+ public open class Builder internal constructor(context: Context) {
private val context: Context
private var name: String? = null
private var callback: Callback? = null
@@ -281,7 +281,7 @@
*
* @return The [Configuration] instance
*/
- open fun build(): Configuration {
+ public open fun build(): Configuration {
val callback = callback
requireNotNull(callback) { "Must set a callback to create the configuration." }
require(!useNoBackupDirectory || !name.isNullOrEmpty()) {
@@ -305,13 +305,15 @@
* @param name Name of the database file, or null for an in-memory database.
* @return This builder instance.
*/
- open fun name(name: String?): Builder = apply { this.name = name }
+ public open fun name(name: String?): Builder = apply { this.name = name }
/**
* @param callback The callback class to handle creation, upgrade and downgrade.
* @return This builder instance.
*/
- open fun callback(callback: Callback): Builder = apply { this.callback = callback }
+ public open fun callback(callback: Callback): Builder = apply {
+ this.callback = callback
+ }
/**
* Sets whether to use a no backup directory or not.
@@ -320,7 +322,7 @@
* no-backup directory.
* @return This builder instance.
*/
- open fun noBackupDirectory(useNoBackupDirectory: Boolean): Builder = apply {
+ public open fun noBackupDirectory(useNoBackupDirectory: Boolean): Builder = apply {
this.useNoBackupDirectory = useNoBackupDirectory
}
@@ -332,32 +334,33 @@
* case that it cannot be opened.
* @return this
*/
- open fun allowDataLossOnRecovery(allowDataLossOnRecovery: Boolean): Builder = apply {
- this.allowDataLossOnRecovery = allowDataLossOnRecovery
- }
+ public open fun allowDataLossOnRecovery(allowDataLossOnRecovery: Boolean): Builder =
+ apply {
+ this.allowDataLossOnRecovery = allowDataLossOnRecovery
+ }
}
- companion object {
+ public companion object {
/**
* Creates a new Configuration.Builder to create an instance of Configuration.
*
* @param context to use to open or create the database.
*/
@JvmStatic
- fun builder(context: Context): Builder {
+ public fun builder(context: Context): Builder {
return Builder(context)
}
}
}
/** Factory class to create instances of [SupportSQLiteOpenHelper] using [Configuration]. */
- fun interface Factory {
+ public fun interface Factory {
/**
* Creates an instance of [SupportSQLiteOpenHelper] using the given configuration.
*
* @param configuration The configuration to use while creating the open helper.
* @return A SupportSQLiteOpenHelper which can be used to open a database.
*/
- fun create(configuration: Configuration): SupportSQLiteOpenHelper
+ public fun create(configuration: Configuration): SupportSQLiteOpenHelper
}
}
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteProgram.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteProgram.android.kt
index 9bff8c4..572879d 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteProgram.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteProgram.android.kt
@@ -19,14 +19,14 @@
/** An interface to map the behavior of [android.database.sqlite.SQLiteProgram]. */
@Suppress("AcronymName") // SQL is a known term and should remain capitalized
-interface SupportSQLiteProgram : Closeable {
+public interface SupportSQLiteProgram : Closeable {
/**
* Bind a NULL value to this statement. The value remains bound until [.clearBindings] is
* called.
*
* @param index The 1-based index to the parameter to bind null to
*/
- fun bindNull(index: Int)
+ public fun bindNull(index: Int)
/**
* Bind a long value to this statement. The value remains bound until [clearBindings] is called.
@@ -35,7 +35,7 @@
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
- fun bindLong(index: Int, value: Long)
+ public fun bindLong(index: Int, value: Long)
/**
* Bind a double value to this statement. The value remains bound until [.clearBindings] is
@@ -44,7 +44,7 @@
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
- fun bindDouble(index: Int, value: Double)
+ public fun bindDouble(index: Int, value: Double)
/**
* Bind a String value to this statement. The value remains bound until [.clearBindings] is
@@ -53,7 +53,7 @@
* @param index The 1-based index to the parameter to bind
* @param value The value to bind, must not be null
*/
- fun bindString(index: Int, value: String)
+ public fun bindString(index: Int, value: String)
/**
* Bind a byte array value to this statement. The value remains bound until [.clearBindings] is
@@ -62,8 +62,8 @@
* @param index The 1-based index to the parameter to bind
* @param value The value to bind, must not be null
*/
- fun bindBlob(index: Int, value: ByteArray)
+ public fun bindBlob(index: Int, value: ByteArray)
/** Clears all existing bindings. Unset bindings are treated as NULL. */
- fun clearBindings()
+ public fun clearBindings()
}
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQuery.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQuery.android.kt
index b5ffb7e..3db42f5 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQuery.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQuery.android.kt
@@ -20,20 +20,20 @@
* [android.database.sqlite.SQLiteDatabase.rawQuery] because it allows binding type safe parameters.
*/
@Suppress("AcronymName") // SQL is a known term and should remain capitalized
-interface SupportSQLiteQuery {
+public interface SupportSQLiteQuery {
/** The SQL query. This query can have placeholders(?) for bind arguments. */
- val sql: String
+ public val sql: String
/**
* Callback to bind the query parameters to the compiled statement.
*
* @param statement The compiled statement
*/
- fun bindTo(statement: SupportSQLiteProgram)
+ public fun bindTo(statement: SupportSQLiteProgram)
/**
* Is the number of arguments in this query. This is equal to the number of placeholders in the
* query string. See: https://www.sqlite.org/c3ref/bind_blob.html for details.
*/
- val argCount: Int
+ public val argCount: Int
}
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQueryBuilder.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQueryBuilder.android.kt
index 60f5817..770820c 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQueryBuilder.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQueryBuilder.android.kt
@@ -19,7 +19,7 @@
/** A simple query builder to create SQL SELECT queries. */
@Suppress("AcronymName") // SQL is a known term and should remain capitalized
-class SupportSQLiteQueryBuilder private constructor(private val table: String) {
+public class SupportSQLiteQueryBuilder private constructor(private val table: String) {
private var distinct = false
private var columns: Array<out String>? = null
private var selection: String? = null
@@ -34,7 +34,7 @@
*
* @return this
*/
- fun distinct(): SupportSQLiteQueryBuilder = apply { this.distinct = true }
+ public fun distinct(): SupportSQLiteQueryBuilder = apply { this.distinct = true }
/**
* Sets the given list of columns as the columns that will be returned.
@@ -42,7 +42,7 @@
* @param columns The list of column names that should be returned.
* @return this
*/
- fun columns(columns: Array<out String>?): SupportSQLiteQueryBuilder = apply {
+ public fun columns(columns: Array<out String>?): SupportSQLiteQueryBuilder = apply {
this.columns = columns
}
@@ -53,11 +53,13 @@
* @param bindArgs The list of bind arguments to match against these columns
* @return this
*/
- fun selection(selection: String?, bindArgs: Array<out Any?>?): SupportSQLiteQueryBuilder =
- apply {
- this.selection = selection
- this.bindArgs = bindArgs
- }
+ public fun selection(
+ selection: String?,
+ bindArgs: Array<out Any?>?
+ ): SupportSQLiteQueryBuilder = apply {
+ this.selection = selection
+ this.bindArgs = bindArgs
+ }
/**
* Adds a GROUP BY statement.
@@ -65,7 +67,9 @@
* @param groupBy The value of the GROUP BY statement.
* @return this
*/
- fun groupBy(groupBy: String?): SupportSQLiteQueryBuilder = apply { this.groupBy = groupBy }
+ public fun groupBy(groupBy: String?): SupportSQLiteQueryBuilder = apply {
+ this.groupBy = groupBy
+ }
/**
* Adds a HAVING statement. You must also provide [groupBy] for this to work.
@@ -73,7 +77,7 @@
* @param having The having clause.
* @return this
*/
- fun having(having: String?): SupportSQLiteQueryBuilder = apply { this.having = having }
+ public fun having(having: String?): SupportSQLiteQueryBuilder = apply { this.having = having }
/**
* Adds an ORDER BY statement.
@@ -81,7 +85,9 @@
* @param orderBy The order clause.
* @return this
*/
- fun orderBy(orderBy: String?): SupportSQLiteQueryBuilder = apply { this.orderBy = orderBy }
+ public fun orderBy(orderBy: String?): SupportSQLiteQueryBuilder = apply {
+ this.orderBy = orderBy
+ }
/**
* Adds a LIMIT statement.
@@ -89,7 +95,7 @@
* @param limit The limit value.
* @return this
*/
- fun limit(limit: String): SupportSQLiteQueryBuilder = apply {
+ public fun limit(limit: String): SupportSQLiteQueryBuilder = apply {
val patternMatches = limitPattern.matcher(limit).matches()
require(limit.isEmpty() || patternMatches) { "invalid LIMIT clauses:$limit" }
this.limit = limit
@@ -100,7 +106,7 @@
*
* @return a new query
*/
- fun create(): SupportSQLiteQuery {
+ public fun create(): SupportSQLiteQuery {
require(!groupBy.isNullOrEmpty() || having.isNullOrEmpty()) {
"HAVING clauses are only permitted when using a groupBy clause"
}
@@ -146,7 +152,7 @@
append(' ')
}
- companion object {
+ public companion object {
private val limitPattern = Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?")
/**
@@ -156,7 +162,7 @@
* @return A builder to create a query.
*/
@JvmStatic
- fun builder(tableName: String): SupportSQLiteQueryBuilder {
+ public fun builder(tableName: String): SupportSQLiteQueryBuilder {
return SupportSQLiteQueryBuilder(tableName)
}
}
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteStatement.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteStatement.android.kt
index 829612c..a5c36ae 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteStatement.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteStatement.android.kt
@@ -17,14 +17,14 @@
/** An interface to map the behavior of [android.database.sqlite.SQLiteStatement]. */
@Suppress("AcronymName") // SQL is a known term and should remain capitalized
-interface SupportSQLiteStatement : SupportSQLiteProgram {
+public interface SupportSQLiteStatement : SupportSQLiteProgram {
/**
* Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example
* CREATE / DROP table, view, trigger, index etc.
*
* @throws [android.database.SQLException] If the SQL string is invalid for some reason
*/
- fun execute()
+ public fun execute()
/**
* Execute this SQL statement, if the the number of rows affected by execution of this SQL
@@ -33,7 +33,7 @@
* @return the number of rows affected by this SQL statement execution.
* @throws [android.database.SQLException] If the SQL string is invalid for some reason
*/
- fun executeUpdateDelete(): Int
+ public fun executeUpdateDelete(): Int
/**
* Execute this SQL statement and return the ID of the row inserted due to this call. The SQL
@@ -42,7 +42,7 @@
* @return the row ID of the last row inserted, if this insert is successful. -1 otherwise.
* @throws [android.database.SQLException] If the SQL string is invalid for some reason
*/
- fun executeInsert(): Long
+ public fun executeInsert(): Long
/**
* Execute a statement that returns a 1 by 1 table with a numeric value. For example, SELECT
@@ -51,7 +51,7 @@
* @return The result of the query.
* @throws [android.database.sqlite.SQLiteDoneException] if the query returns zero rows
*/
- fun simpleQueryForLong(): Long
+ public fun simpleQueryForLong(): Long
/**
* Execute a statement that returns a 1 by 1 table with a text value. For example, SELECT
@@ -60,5 +60,5 @@
* @return The result of the query.
* @throws [android.database.sqlite.SQLiteDoneException] if the query returns zero rows
*/
- fun simpleQueryForString(): String?
+ public fun simpleQueryForString(): String?
}
diff --git a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLite.kt b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLite.kt
index b997417..f930e84 100644
--- a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLite.kt
+++ b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLite.kt
@@ -22,14 +22,14 @@
/** Executes a single SQL statement that returns no values. */
@Suppress("AcronymName") // SQL is a known term and should remain capitalized
-fun SQLiteConnection.execSQL(sql: String) {
+public fun SQLiteConnection.execSQL(sql: String) {
prepare(sql).use { it.step() }
}
/** Use the receiver statement within the [block] and closes it once it is done. */
// TODO(b/315461431): Migrate to a Closeable interface in KMP
@Suppress("AcronymName") // SQL is a known term and should remain capitalized
-inline fun <R> SQLiteStatement.use(block: (SQLiteStatement) -> R): R {
+public inline fun <R> SQLiteStatement.use(block: (SQLiteStatement) -> R): R {
try {
return block.invoke(this)
} finally {
@@ -39,7 +39,7 @@
/** Throws a [SQLiteException] with its message formed by the given [errorCode] amd [errorMsg]. */
@Suppress("AcronymName") // SQL is a known term and should remain capitalized
-fun throwSQLiteException(errorCode: Int, errorMsg: String?): Nothing {
+public fun throwSQLiteException(errorCode: Int, errorMsg: String?): Nothing {
val message = buildString {
append("Error code: $errorCode")
if (errorMsg != null) {
diff --git a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteConnection.kt b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteConnection.kt
index 5922ca2..128ef24 100644
--- a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteConnection.kt
+++ b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteConnection.kt
@@ -26,7 +26,7 @@
*/
// TODO(b/315461431): No common Closeable interface in KMP
@Suppress("NotCloseable", "AcronymName") // SQL is a known term and should remain capitalized
-interface SQLiteConnection {
+public interface SQLiteConnection {
/**
* Prepares a new SQL statement.
*
@@ -35,7 +35,7 @@
* @param sql the SQL statement to prepare
* @return the prepared statement.
*/
- fun prepare(sql: String): SQLiteStatement
+ public fun prepare(sql: String): SQLiteStatement
/**
* Closes the database connection.
@@ -43,5 +43,5 @@
* Once a connection is closed it should no longer be used. Calling this function on an already
* closed database connection is a no-op.
*/
- fun close()
+ public fun close()
}
diff --git a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteDriver.kt b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteDriver.kt
index a0fa75c..5cb23bf 100644
--- a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteDriver.kt
+++ b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteDriver.kt
@@ -18,12 +18,12 @@
/** An interface to open database connections. */
@Suppress("AcronymName") // SQL is a known term and should remain capitalized
-interface SQLiteDriver {
+public interface SQLiteDriver {
/**
* Opens a new database connection.
*
* @param fileName Name of the database file.
* @return the database connection.
*/
- fun open(fileName: String): SQLiteConnection
+ public fun open(fileName: String): SQLiteConnection
}
diff --git a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteException.kt b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteException.kt
index 1276395..c2d575e 100644
--- a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteException.kt
+++ b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteException.kt
@@ -23,6 +23,6 @@
*
* See [Result and Error codes](https://www.sqlite.org/rescode.html)
*/
-expect class SQLiteException
+public expect class SQLiteException
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
constructor(message: String) : RuntimeException
diff --git a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
index 039d211..65ba44c 100644
--- a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
+++ b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
@@ -26,14 +26,14 @@
*/
// TODO(b/315461431): No common Closeable interface in KMP
@Suppress("NotCloseable", "AcronymName") // SQL is a known term and should remain capitalized
-interface SQLiteStatement {
+public interface SQLiteStatement {
/**
* Binds a ByteArray value to this statement at an index.
*
* @param index the 1-based index of the parameter to bind
* @param value the value to bind
*/
- fun bindBlob(index: Int, value: ByteArray)
+ public fun bindBlob(index: Int, value: ByteArray)
/**
* Binds a Double value to this statement at an index.
@@ -41,7 +41,7 @@
* @param index the 1-based index of the parameter to bind
* @param value the value to bind
*/
- fun bindDouble(index: Int, value: Double)
+ public fun bindDouble(index: Int, value: Double)
/**
* Binds a Float value to this statement at an index.
@@ -49,7 +49,7 @@
* @param index the 1-based index of the parameter to bind
* @param value the value to bind
*/
- fun bindFloat(index: Int, value: Float) {
+ public fun bindFloat(index: Int, value: Float) {
bindDouble(index, value.toDouble())
}
@@ -59,7 +59,7 @@
* @param index the 1-based index of the parameter to bind
* @param value the value to bind
*/
- fun bindLong(index: Int, value: Long)
+ public fun bindLong(index: Int, value: Long)
/**
* Binds a Int value to this statement at an index.
@@ -67,7 +67,7 @@
* @param index the 1-based index of the parameter to bind
* @param value the value to bind
*/
- fun bindInt(index: Int, value: Int) {
+ public fun bindInt(index: Int, value: Int) {
bindLong(index, value.toLong())
}
@@ -77,7 +77,7 @@
* @param index the 1-based index of the parameter to bind
* @param value the value to bind
*/
- fun bindBoolean(index: Int, value: Boolean) {
+ public fun bindBoolean(index: Int, value: Boolean) {
bindLong(index, if (value) 1L else 0L)
}
@@ -87,14 +87,14 @@
* @param index the 1-based index of the parameter to bind
* @param value the value to bind
*/
- fun bindText(index: Int, value: String)
+ public fun bindText(index: Int, value: String)
/**
* Binds a NULL value to this statement at an index.
*
* @param index the 1-based index of the parameter to bind
*/
- fun bindNull(index: Int)
+ public fun bindNull(index: Int)
/**
* Returns the value of the column at [index] as a ByteArray.
@@ -102,7 +102,7 @@
* @param index the 0-based index of the column
* @return the value of the column
*/
- fun getBlob(index: Int): ByteArray
+ public fun getBlob(index: Int): ByteArray
/**
* Returns the value of the column at [index] as a Double.
@@ -110,7 +110,7 @@
* @param index the 0-based index of the column
* @return the value of the column
*/
- fun getDouble(index: Int): Double
+ public fun getDouble(index: Int): Double
/**
* Returns the value of the column at [index] as a Float.
@@ -118,7 +118,7 @@
* @param index the 0-based index of the column
* @return the value of the column
*/
- fun getFloat(index: Int): Float {
+ public fun getFloat(index: Int): Float {
return getDouble(index).toFloat()
}
@@ -128,7 +128,7 @@
* @param index the 0-based index of the column
* @return the value of the column
*/
- fun getLong(index: Int): Long
+ public fun getLong(index: Int): Long
/**
* Returns the value of the column at [index] as a Int.
@@ -136,7 +136,7 @@
* @param index the 0-based index of the column
* @return the value of the column
*/
- fun getInt(index: Int): Int {
+ public fun getInt(index: Int): Int {
return getLong(index).toInt()
}
@@ -146,7 +146,7 @@
* @param index the 0-based index of the column
* @return the value of the column
*/
- fun getBoolean(index: Int): Boolean {
+ public fun getBoolean(index: Int): Boolean {
return getLong(index) != 0L
}
@@ -156,7 +156,7 @@
* @param index the 0-based index of the column
* @return the value of the column
*/
- fun getText(index: Int): String
+ public fun getText(index: Int): String
/**
* Returns true if the value of the column at [index] is NULL.
@@ -164,14 +164,14 @@
* @param index the 0-based index of the column
* @return true if the column value is NULL, false otherwise
*/
- fun isNull(index: Int): Boolean
+ public fun isNull(index: Int): Boolean
/**
* Returns the number of columns in the result of the statement.
*
* @return the number of columns
*/
- fun getColumnCount(): Int
+ public fun getColumnCount(): Int
/**
* Returns the name of a column at [index] in the result of the statement.
@@ -179,14 +179,14 @@
* @param index the 0-based index of the column
* @return the name of the column
*/
- fun getColumnName(index: Int): String
+ public fun getColumnName(index: Int): String
/**
* Returns the name of the columns in the result of the statement ordered by their index.
*
* @return the names of the columns
*/
- fun getColumnNames(): List<String> {
+ public fun getColumnNames(): List<String> {
return List(getColumnCount()) { i -> getColumnName(i) }
}
@@ -199,16 +199,16 @@
*
* @return true if there are more rows to evaluate or false if the statement is done executing
*/
- fun step(): Boolean
+ public fun step(): Boolean
/**
* Resets the prepared statement back to initial state so that it can be re-executed via [step].
* Any parameter bound via the bind*() APIs will retain their value.
*/
- fun reset()
+ public fun reset()
/** Clears all parameter bindings. Unset bindings are treated as NULL. */
- fun clearBindings()
+ public fun clearBindings()
/**
* Closes the statement.
@@ -216,5 +216,5 @@
* Once a statement is closed it should no longer be used. Calling this function on an already
* closed statement is a no-op.
*/
- fun close()
+ public fun close()
}
diff --git a/sqlite/sqlite/src/jvmMain/kotlin/androidx/sqlite/SQLiteException.jvm.kt b/sqlite/sqlite/src/jvmMain/kotlin/androidx/sqlite/SQLiteException.jvm.kt
index cf6e78c..a3f49cb 100644
--- a/sqlite/sqlite/src/jvmMain/kotlin/androidx/sqlite/SQLiteException.jvm.kt
+++ b/sqlite/sqlite/src/jvmMain/kotlin/androidx/sqlite/SQLiteException.jvm.kt
@@ -18,6 +18,6 @@
import androidx.annotation.RestrictTo
-actual class SQLiteException
+public actual class SQLiteException
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
actual constructor(message: String) : RuntimeException(message)
diff --git a/sqlite/sqlite/src/nativeMain/kotlin/androidx/sqlite/SQLiteException.native.kt b/sqlite/sqlite/src/nativeMain/kotlin/androidx/sqlite/SQLiteException.native.kt
index cf6e78c..a3f49cb 100644
--- a/sqlite/sqlite/src/nativeMain/kotlin/androidx/sqlite/SQLiteException.native.kt
+++ b/sqlite/sqlite/src/nativeMain/kotlin/androidx/sqlite/SQLiteException.native.kt
@@ -18,6 +18,6 @@
import androidx.annotation.RestrictTo
-actual class SQLiteException
+public actual class SQLiteException
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
actual constructor(message: String) : RuntimeException(message)
diff --git a/testutils/testutils-datastore/build.gradle b/testutils/testutils-datastore/build.gradle
index befacf5..7d8f19b 100644
--- a/testutils/testutils-datastore/build.gradle
+++ b/testutils/testutils-datastore/build.gradle
@@ -32,6 +32,8 @@
mac()
linux()
ios()
+ watchos()
+ tvos()
sourceSets {
commonMain {
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AnimatedCornerShapeScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AnimatedCornerShapeScreenshotTest.kt
new file mode 100644
index 0000000..df52e66
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AnimatedCornerShapeScreenshotTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import android.os.Build
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.shape.AbsoluteCutCornerShape
+import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape
+import androidx.compose.foundation.shape.CutCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+class AnimatedCornerShapeScreenshotTest {
+
+ @get:Rule val rule = createComposeRule()
+
+ @get:Rule val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
+
+ @get:Rule val testName = TestName()
+
+ @Test
+ fun cutCornerShapeLtr() = verifyScreenshot {
+ FilledIconButton(
+ onClick = {},
+ shape =
+ CutCornerShape(topStart = 2.dp, topEnd = 4.dp, bottomEnd = 6.dp, bottomStart = 8.dp)
+ ) {}
+ }
+
+ @Test
+ fun cutCornerShapeRtl() =
+ verifyScreenshot(layoutDirection = LayoutDirection.Rtl) {
+ FilledIconButton(
+ onClick = {},
+ shape =
+ CutCornerShape(
+ topStart = 2.dp,
+ topEnd = 4.dp,
+ bottomEnd = 6.dp,
+ bottomStart = 8.dp
+ )
+ ) {}
+ }
+
+ @Test
+ fun absoluteCutCornerShapeLtr() = verifyScreenshot {
+ FilledIconButton(
+ onClick = {},
+ shape =
+ AbsoluteCutCornerShape(
+ topLeft = 2.dp,
+ topRight = 4.dp,
+ bottomRight = 6.dp,
+ bottomLeft = 8.dp
+ )
+ ) {}
+ }
+
+ @Test
+ fun absoluteCutCornerShapeRtl() =
+ verifyScreenshot(layoutDirection = LayoutDirection.Rtl) {
+ FilledIconButton(
+ onClick = {},
+ shape =
+ AbsoluteCutCornerShape(
+ topLeft = 2.dp,
+ topRight = 4.dp,
+ bottomRight = 6.dp,
+ bottomLeft = 8.dp
+ )
+ ) {}
+ }
+
+ @Test
+ fun absoluteRoundedCornerShapeLtr() = verifyScreenshot {
+ FilledIconButton(
+ onClick = {},
+ shape =
+ AbsoluteRoundedCornerShape(
+ topLeft = 2.dp,
+ topRight = 4.dp,
+ bottomRight = 6.dp,
+ bottomLeft = 8.dp
+ )
+ ) {}
+ }
+
+ @Test
+ fun absoluteRoundedCornerShapeRtl() =
+ verifyScreenshot(layoutDirection = LayoutDirection.Rtl) {
+ FilledIconButton(
+ onClick = {},
+ shape =
+ AbsoluteRoundedCornerShape(
+ topLeft = 2.dp,
+ topRight = 4.dp,
+ bottomRight = 6.dp,
+ bottomLeft = 8.dp
+ )
+ ) {}
+ }
+
+ private fun verifyScreenshot(
+ layoutDirection: LayoutDirection = LayoutDirection.Ltr,
+ content: @Composable () -> Unit
+ ) {
+ rule.setContentWithTheme {
+ CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
+ Box(modifier = Modifier.background(Color.Black).testTag(TEST_TAG)) { content() }
+ }
+ }
+
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, testName.methodName)
+ }
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AnimatedCornerShapeTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AnimatedCornerShapeTest.kt
new file mode 100644
index 0000000..5886659
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AnimatedCornerShapeTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import androidx.compose.foundation.shape.AbsoluteCutCornerShape
+import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape
+import androidx.compose.foundation.shape.CutCornerShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+
+class AnimatedCornerShapeTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun convertsRoundedCornerShape() {
+ val roundedCornerShape = RoundedCornerShape(10.dp)
+
+ val roundedPolygon =
+ roundedCornerShape.toRoundedPolygonOrNull(
+ size = Size(100f, 100f),
+ density = Density(density = 2f),
+ layoutDirection = LayoutDirection.Ltr
+ )
+
+ assertThat(roundedPolygon).isNotNull()
+ assertThat(roundedPolygon!!.calculateBounds()).isEqualTo(floatArrayOf(0f, 0f, 1f, 1f))
+ assertThat(roundedPolygon.cubics.size).isEqualTo(9)
+
+ val points =
+ roundedPolygon.cubics.flatMap {
+ listOf(Offset(it.anchor0X, it.anchor0Y), Offset(it.anchor1X, it.anchor1Y))
+ }
+ assertThat(points)
+ .containsAtLeast(
+ Offset(1.0f, 0.8f),
+ Offset(0.8f, 1.0f),
+ Offset(0.2f, 1.0f),
+ Offset(0.0f, 0.8f),
+ Offset(0.0f, 0.2f),
+ Offset(0.2f, 0.0f),
+ Offset(0.8f, 0.0f),
+ Offset(1.0f, 0.2f),
+ )
+ }
+
+ @Test
+ fun convertsCutCornerShape() {
+ val cutCornerShape = CutCornerShape(10.dp)
+
+ val roundedPolygon =
+ cutCornerShape.toRoundedPolygonOrNull(
+ size = Size(100f, 100f),
+ density = Density(density = 2f),
+ layoutDirection = LayoutDirection.Ltr
+ )
+
+ assertThat(roundedPolygon).isNotNull()
+ assertThat(roundedPolygon!!.calculateBounds()).isEqualTo(floatArrayOf(0f, 0f, 1f, 1f))
+ assertThat(roundedPolygon.cubics.size).isEqualTo(8)
+
+ val points =
+ roundedPolygon.cubics.flatMap {
+ listOf(Offset(it.anchor0X, it.anchor0Y), Offset(it.anchor1X, it.anchor1Y))
+ }
+ assertThat(points)
+ .containsAtLeast(
+ Offset(1.0f, 0.8f),
+ Offset(0.8f, 1.0f),
+ Offset(0.2f, 1.0f),
+ Offset(0.0f, 0.8f),
+ Offset(0.0f, 0.2f),
+ Offset(0.2f, 0.0f),
+ Offset(0.8f, 0.0f),
+ Offset(1.0f, 0.2f),
+ )
+ }
+
+ @Test
+ fun convertsAbsoluteRoundedCornerShape() {
+ val roundedCornerShape = AbsoluteRoundedCornerShape(5.dp)
+
+ val roundedPolygon =
+ roundedCornerShape.toRoundedPolygonOrNull(
+ size = Size(100f, 100f),
+ density = Density(density = 2f),
+ layoutDirection = LayoutDirection.Ltr
+ )
+
+ assertThat(roundedPolygon).isNotNull()
+ assertThat(roundedPolygon!!.calculateBounds()).isEqualTo(floatArrayOf(0f, 0f, 1f, 1f))
+ assertThat(roundedPolygon.cubics.size).isEqualTo(9)
+
+ val points =
+ roundedPolygon.cubics.flatMap {
+ listOf(Offset(it.anchor0X, it.anchor0Y), Offset(it.anchor1X, it.anchor1Y))
+ }
+ assertThat(points)
+ .containsAtLeast(
+ Offset(1.0f, 0.9f),
+ Offset(0.9f, 1.0f),
+ Offset(0.1f, 1.0f),
+ Offset(0.0f, 0.9f),
+ Offset(0.0f, 0.1f),
+ Offset(0.1f, 0.0f),
+ Offset(0.9f, 0.0f),
+ Offset(1.0f, 0.1f),
+ )
+ }
+
+ @Test
+ fun convertsAbsoluteCutCornerShape() {
+ val cutCornerShape = AbsoluteCutCornerShape(5.dp)
+
+ val roundedPolygon =
+ cutCornerShape.toRoundedPolygonOrNull(
+ size = Size(100f, 100f),
+ density = Density(density = 2f),
+ layoutDirection = LayoutDirection.Ltr
+ )
+
+ assertThat(roundedPolygon).isNotNull()
+ assertThat(roundedPolygon!!.calculateBounds()).isEqualTo(floatArrayOf(0f, 0f, 1f, 1f))
+ assertThat(roundedPolygon.cubics.size).isEqualTo(8)
+
+ val points =
+ roundedPolygon.cubics.flatMap {
+ listOf(Offset(it.anchor0X, it.anchor0Y), Offset(it.anchor1X, it.anchor1Y))
+ }
+ assertThat(points)
+ .containsAtLeast(
+ Offset(1.0f, 0.9f),
+ Offset(0.9f, 1.0f),
+ Offset(0.1f, 1.0f),
+ Offset(0.0f, 0.9f),
+ Offset(0.0f, 0.1f),
+ Offset(0.1f, 0.0f),
+ Offset(0.9f, 0.0f),
+ Offset(1.0f, 0.1f),
+ )
+ }
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimatedCornerShape.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimatedCornerShape.kt
index 70fa44b..afca2cd 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimatedCornerShape.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimatedCornerShape.kt
@@ -170,7 +170,7 @@
* @param size The size of the final Composable such as a Button.
* @param density The density of the composition.
*/
-private fun CornerBasedShape.toRoundedPolygonOrNull(
+internal fun CornerBasedShape.toRoundedPolygonOrNull(
size: Size,
density: Density,
layoutDirection: LayoutDirection