From 649f1fffbe15ea542dc66a892924ee91f93c3120 Mon Sep 17 00:00:00 2001 From: Christian Schmitz Date: Mon, 8 Nov 2021 14:30:01 +0100 Subject: [PATCH] feat: extend AString from Parcelable --- README.md | 4 +- android.gradle | 5 +- astring/build.gradle | 4 +- .../xyz/tynn/astring/AssertParcelable.java | 24 ++++++++++ .../tynn/astring/ParcelableAStringTest.java | 47 +++++++++++++++++++ .../main/java/xyz/tynn/astring/AString.java | 8 +++- .../xyz/tynn/astring/CharSequenceWrapper.java | 22 +++++++++ .../xyz/tynn/astring/NullValueWrapper.java | 21 +++++++++ .../QuantityStringResourceDelegate.java | 22 +++++++++ .../astring/QuantityTextResourceDelegate.java | 20 ++++++++ .../tynn/astring/StringResourceDelegate.java | 21 +++++++++ .../tynn/astring/TextResourceDelegate.java | 19 ++++++++ .../kotlin/xyz/tynn/astring/AStringFactory.kt | 11 +++++ .../xyz/tynn/astring/NullValueWrapperTest.kt | 31 +++++------- build.gradle | 7 ++- example/base/src/main/AndroidManifest.xml | 2 - example/java/src/main/AndroidManifest.xml | 11 ++++- example/kotlin/src/main/AndroidManifest.xml | 11 ++++- extension/appcompat/build.gradle | 2 +- extension/core/build.gradle | 2 +- extension/material/build.gradle | 2 +- testing/build.gradle | 3 +- wrapper.gradle | 2 +- 23 files changed, 261 insertions(+), 40 deletions(-) create mode 100644 astring/src/androidTest/java/xyz/tynn/astring/AssertParcelable.java create mode 100644 astring/src/androidTest/java/xyz/tynn/astring/ParcelableAStringTest.java diff --git a/README.md b/README.md index 1428673..36a84b4 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ [![Build][build-shield]][build] [![Download][download-shield]][download] [![API][api-shield]][api] -###### A context aware string abstraction for _Android_ +###### A context aware parcelable string abstraction for _Android_ ``` -public interface AString { +public interface AString extends Parcelable { @Nullable CharSequence invoke(@NonNull Context context); } diff --git a/android.gradle b/android.gradle index 311269b..3c34b32 100644 --- a/android.gradle +++ b/android.gradle @@ -1,8 +1,9 @@ android { - compileSdkVersion 30 + compileSdkVersion 31 defaultConfig { minSdkVersion 19 - targetSdkVersion 30 + targetSdkVersion 31 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } } diff --git a/astring/build.gradle b/astring/build.gradle index 0bc1468..f97c38c 100644 --- a/astring/build.gradle +++ b/astring/build.gradle @@ -4,7 +4,9 @@ plugins { } dependencies { - api 'androidx.fragment:fragment:1.3.4' + api 'androidx.fragment:fragment:1.3.6' testImplementation project(':testing') + + androidTestImplementation 'androidx.test:rules:1.4.0' } diff --git a/astring/src/androidTest/java/xyz/tynn/astring/AssertParcelable.java b/astring/src/androidTest/java/xyz/tynn/astring/AssertParcelable.java new file mode 100644 index 0000000..cb029e3 --- /dev/null +++ b/astring/src/androidTest/java/xyz/tynn/astring/AssertParcelable.java @@ -0,0 +1,24 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring; + +import static android.os.Parcel.obtain; +import static org.junit.Assert.assertEquals; + +import android.os.Parcel; +import android.os.Parcelable; + +class AssertParcelable { + + private static ClassLoader getClassloader() { + return AssertParcelable.class.getClassLoader(); + } + + static void assertParcelable(T expected) { + Parcel parcel = obtain(); + parcel.writeValue(expected); + parcel.setDataPosition(0); + assertEquals(expected, parcel.readValue(getClassloader())); + } +} diff --git a/astring/src/androidTest/java/xyz/tynn/astring/ParcelableAStringTest.java b/astring/src/androidTest/java/xyz/tynn/astring/ParcelableAStringTest.java new file mode 100644 index 0000000..cc867d6 --- /dev/null +++ b/astring/src/androidTest/java/xyz/tynn/astring/ParcelableAStringTest.java @@ -0,0 +1,47 @@ +// Copyright 2021 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring; + +import static xyz.tynn.astring.AssertParcelable.assertParcelable; + +import org.junit.Test; + +import java.util.Date; + +public class ParcelableAStringTest { + + public static final int RES_ID = 123; + public static final int QUANTITY = 456; + public static final Object[] FORMAT_ARGS = {"arg1", 2, 3L, 4.5, 6F, new Date()}; + + @Test + public void CharSequence_should_implement_parcelable() { + assertParcelable(new CharSequenceWrapper("test-string")); + } + + @Test + public void NullValueWrapper_should_implement_parcelable() { + assertParcelable(NullValueWrapper.I); + } + + @Test + public void QuantityStringResourceDelegate_should_implement_parcelable() { + assertParcelable(new QuantityStringResourceDelegate(RES_ID, QUANTITY, FORMAT_ARGS)); + } + + @Test + public void QuantityTextResourceDelegate_should_implement_parcelable() { + assertParcelable(new QuantityTextResourceDelegate(RES_ID, QUANTITY)); + } + + @Test + public void StringResourceDelegate_should_implement_parcelable() { + assertParcelable(new StringResourceDelegate(RES_ID, FORMAT_ARGS)); + } + + @Test + public void TextResourceDelegate_should_implement_parcelable() { + assertParcelable(new TextResourceDelegate(RES_ID)); + } +} diff --git a/astring/src/main/java/xyz/tynn/astring/AString.java b/astring/src/main/java/xyz/tynn/astring/AString.java index 92fd2b7..e1887b0 100644 --- a/astring/src/main/java/xyz/tynn/astring/AString.java +++ b/astring/src/main/java/xyz/tynn/astring/AString.java @@ -4,6 +4,7 @@ package xyz.tynn.astring; import android.content.Context; +import android.os.Parcelable; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -18,7 +19,7 @@ * {@code AString} is almost always used from the main thread, * therefore all implementations must be non-blocking since */ -public interface AString { +public interface AString extends Parcelable { /** * Provides a context sensitive string @@ -28,5 +29,10 @@ public interface AString { */ @Nullable CharSequence invoke(@NonNull Context context); + + @Override + default int describeContents() { + return 0; + } } diff --git a/astring/src/main/java/xyz/tynn/astring/CharSequenceWrapper.java b/astring/src/main/java/xyz/tynn/astring/CharSequenceWrapper.java index 3ccee85..240106a 100644 --- a/astring/src/main/java/xyz/tynn/astring/CharSequenceWrapper.java +++ b/astring/src/main/java/xyz/tynn/astring/CharSequenceWrapper.java @@ -3,7 +3,11 @@ package xyz.tynn.astring; +import static android.text.TextUtils.CHAR_SEQUENCE_CREATOR; + import android.content.Context; +import android.os.Parcel; +import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -45,4 +49,22 @@ public int hashCode() { public String toString() { return "AString(" + "CharSequence(" + string + "))"; } + + @Override + public void writeToParcel(Parcel dest, int flags) { + TextUtils.writeToParcel(string, dest, flags); + } + + public static final Creator CREATOR = new Creator() { + + @Override + public CharSequenceWrapper createFromParcel(Parcel source) { + return new CharSequenceWrapper(CHAR_SEQUENCE_CREATOR.createFromParcel(source)); + } + + @Override + public CharSequenceWrapper[] newArray(int size) { + return new CharSequenceWrapper[size]; + } + }; } diff --git a/astring/src/main/java/xyz/tynn/astring/NullValueWrapper.java b/astring/src/main/java/xyz/tynn/astring/NullValueWrapper.java index 8740f51..e0cec79 100644 --- a/astring/src/main/java/xyz/tynn/astring/NullValueWrapper.java +++ b/astring/src/main/java/xyz/tynn/astring/NullValueWrapper.java @@ -4,6 +4,7 @@ package xyz.tynn.astring; import android.content.Context; +import android.os.Parcel; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -16,6 +17,9 @@ final class NullValueWrapper implements AString { @NonNull static NullValueWrapper I = new NullValueWrapper(); + private NullValueWrapper() { + } + @Nullable @Override public CharSequence invoke(@Nullable Context context) { @@ -37,4 +41,21 @@ public int hashCode() { public String toString() { return "AString(" + "NullValue(" + null + "))"; } + + @Override + public void writeToParcel(Parcel dest, int flags) { + } + + public static final Creator CREATOR = new Creator() { + + @Override + public NullValueWrapper createFromParcel(Parcel source) { + return I; + } + + @Override + public NullValueWrapper[] newArray(int size) { + return new NullValueWrapper[size]; + } + }; } diff --git a/astring/src/main/java/xyz/tynn/astring/QuantityStringResourceDelegate.java b/astring/src/main/java/xyz/tynn/astring/QuantityStringResourceDelegate.java index ca3b4dd..2f6fff9 100644 --- a/astring/src/main/java/xyz/tynn/astring/QuantityStringResourceDelegate.java +++ b/astring/src/main/java/xyz/tynn/astring/QuantityStringResourceDelegate.java @@ -5,6 +5,7 @@ import android.content.Context; import android.content.res.Resources; +import android.os.Parcel; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -62,4 +63,25 @@ public String toString() { sb.append(',').append(o); return sb.append("))").toString(); } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(resId); + dest.writeInt(quantity); + dest.writeValue(formatArgs); + } + + public static final Creator CREATOR = new Creator() { + + @Override + public QuantityStringResourceDelegate createFromParcel(Parcel source) { + return new QuantityStringResourceDelegate(source.readInt(), source.readInt(), + (Object[]) source.readValue(getClass().getClassLoader())); + } + + @Override + public QuantityStringResourceDelegate[] newArray(int size) { + return new QuantityStringResourceDelegate[size]; + } + }; } diff --git a/astring/src/main/java/xyz/tynn/astring/QuantityTextResourceDelegate.java b/astring/src/main/java/xyz/tynn/astring/QuantityTextResourceDelegate.java index 520a6d8..83d509b 100644 --- a/astring/src/main/java/xyz/tynn/astring/QuantityTextResourceDelegate.java +++ b/astring/src/main/java/xyz/tynn/astring/QuantityTextResourceDelegate.java @@ -4,6 +4,7 @@ package xyz.tynn.astring; import android.content.Context; +import android.os.Parcel; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -46,4 +47,23 @@ public int hashCode() { public String toString() { return "AString(" + "QuantityTextResource(" + resId + ',' + quantity + "))"; } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(resId); + dest.writeInt(quantity); + } + + public static final Creator CREATOR = new Creator() { + + @Override + public QuantityTextResourceDelegate createFromParcel(Parcel source) { + return new QuantityTextResourceDelegate(source.readInt(), source.readInt()); + } + + @Override + public QuantityTextResourceDelegate[] newArray(int size) { + return new QuantityTextResourceDelegate[size]; + } + }; } diff --git a/astring/src/main/java/xyz/tynn/astring/StringResourceDelegate.java b/astring/src/main/java/xyz/tynn/astring/StringResourceDelegate.java index 255e477..14430ce 100644 --- a/astring/src/main/java/xyz/tynn/astring/StringResourceDelegate.java +++ b/astring/src/main/java/xyz/tynn/astring/StringResourceDelegate.java @@ -4,6 +4,7 @@ package xyz.tynn.astring; import android.content.Context; +import android.os.Parcel; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -57,4 +58,24 @@ public String toString() { sb.append(',').append(o); return sb.append("))").toString(); } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(resId); + dest.writeValue(formatArgs); + } + + public static final Creator CREATOR = new Creator() { + + @Override + public StringResourceDelegate createFromParcel(Parcel source) { + return new StringResourceDelegate(source.readInt(), + (Object[]) source.readValue(getClass().getClassLoader())); + } + + @Override + public StringResourceDelegate[] newArray(int size) { + return new StringResourceDelegate[size]; + } + }; } diff --git a/astring/src/main/java/xyz/tynn/astring/TextResourceDelegate.java b/astring/src/main/java/xyz/tynn/astring/TextResourceDelegate.java index 13ded5e..aa94792 100644 --- a/astring/src/main/java/xyz/tynn/astring/TextResourceDelegate.java +++ b/astring/src/main/java/xyz/tynn/astring/TextResourceDelegate.java @@ -4,6 +4,7 @@ package xyz.tynn.astring; import android.content.Context; +import android.os.Parcel; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -44,4 +45,22 @@ public int hashCode() { public String toString() { return "AString(" + "TextResource(" + resId + "))"; } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(resId); + } + + public static final Creator CREATOR = new Creator() { + + @Override + public TextResourceDelegate createFromParcel(Parcel source) { + return new TextResourceDelegate(source.readInt()); + } + + @Override + public TextResourceDelegate[] newArray(int size) { + return new TextResourceDelegate[size]; + } + }; } diff --git a/astring/src/main/kotlin/xyz/tynn/astring/AStringFactory.kt b/astring/src/main/kotlin/xyz/tynn/astring/AStringFactory.kt index ec0090c..3f2447c 100644 --- a/astring/src/main/kotlin/xyz/tynn/astring/AStringFactory.kt +++ b/astring/src/main/kotlin/xyz/tynn/astring/AStringFactory.kt @@ -6,6 +6,8 @@ package xyz.tynn.astring +import android.os.Parcel +import android.text.TextUtils import androidx.annotation.PluralsRes import androidx.annotation.StringRes @@ -16,6 +18,9 @@ public val nullAsAString: AString = NullValueWrapper.I * Creates an `AString` from a `CharSequence?` * * Returns [nullAsAString] for null + * + * **Note** that [TextUtils.writeToParcel] is used to parcel + * the [CharSequence] which might lose some custom styles */ @JvmName("createFromCharSequence") public fun CharSequence?.asAString(): AString = if (this == null) @@ -43,6 +48,9 @@ else QuantityStringResourceDelegate( * Creates an `AString` from a plurals string resource with format arguments * * Returns [nullAsAString] for 0 + * + * **Note** that [Parcel.writeValue] is used to parcel [formatArgs] + * which might throw a [RuntimeException] for un-parcelable values */ @JvmName("createFromQuantityStringResource") public fun QuantityStringResource( @@ -92,6 +100,9 @@ else StringResourceDelegate( * Creates an `AString` from a string resource with format arguments * * Returns [nullAsAString] for 0 + * + * **Note** that [Parcel.writeValue] is used to parcel [formatArgs] + * which might throw a [RuntimeException] for un-parcelable values */ @JvmName("createFromStringResource") public fun StringResource( diff --git a/astring/src/test/kotlin/xyz/tynn/astring/NullValueWrapperTest.kt b/astring/src/test/kotlin/xyz/tynn/astring/NullValueWrapperTest.kt index f975891..5f64100 100644 --- a/astring/src/test/kotlin/xyz/tynn/astring/NullValueWrapperTest.kt +++ b/astring/src/test/kotlin/xyz/tynn/astring/NullValueWrapperTest.kt @@ -8,49 +8,42 @@ import kotlin.test.* internal class NullValueWrapperTest { - @Test - fun `I should be non null`() { - assertNotNull( - NullValueWrapper.I, - ) - } - @Test fun `invoke should return null`() { assertNull( - NullValueWrapper().invoke(null), + NullValueWrapper.I.invoke(null), ) } @Test fun `equals should be true for same type`() { assertTrue { - NullValueWrapper() == NullValueWrapper() + NullValueWrapper.I == mockk() } } @Test - fun `equals should be false for non CharSequenceWrapper`() { + fun `equals should be false for non NullValueWrapper`() { assertFalse { - NullValueWrapper().equals("foo") + NullValueWrapper.I.equals("foo") } assertFalse { - NullValueWrapper() == mockk() + NullValueWrapper.I == mockk() } assertFalse { - NullValueWrapper().equals(mockk()) + NullValueWrapper.I.equals(mockk()) } assertFalse { - NullValueWrapper().equals(mockk()) + NullValueWrapper.I.equals(mockk()) } assertFalse { - NullValueWrapper().equals(mockk()) + NullValueWrapper.I.equals(mockk()) } assertFalse { - NullValueWrapper().equals(mockk()) + NullValueWrapper.I.equals(mockk()) } assertFalse { - NullValueWrapper().equals(mockk()) + NullValueWrapper.I.equals(mockk()) } } @@ -58,7 +51,7 @@ internal class NullValueWrapperTest { fun `hashCode should return 0`() { assertEquals( 0, - NullValueWrapper().hashCode(), + NullValueWrapper.I.hashCode(), ) } @@ -66,7 +59,7 @@ internal class NullValueWrapperTest { fun `toString should return typed string`() { assertEquals( "AString(NullValue(null))", - NullValueWrapper().toString(), + NullValueWrapper.I.toString(), ) } } diff --git a/build.gradle b/build.gradle index bd9417b..918b6b2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,9 @@ plugins { - id 'com.android.library' version '4.2.1' apply false - id 'org.jetbrains.kotlin.android' version '1.5.10' apply false + id 'com.android.library' version '7.0.3' apply false + id 'org.jetbrains.kotlin.android' version '1.5.31' apply false id 'io.github.gradle-nexus.publish-plugin' version '1.1.0' - id 'xyz.tynn.android.maven' version '0.1.0' apply false + id 'xyz.tynn.android.maven' version '0.2.0' apply false id 'com.github.ben-manes.versions' version '0.39.0' - id 'xyz.tynn.idea.fix' version '0.1.2' } apply from: 'publishing.gradle' diff --git a/example/base/src/main/AndroidManifest.xml b/example/base/src/main/AndroidManifest.xml index d33a130..0f61fdc 100644 --- a/example/base/src/main/AndroidManifest.xml +++ b/example/base/src/main/AndroidManifest.xml @@ -2,8 +2,6 @@ package="xyz.tynn.astring.example.base"> diff --git a/example/java/src/main/AndroidManifest.xml b/example/java/src/main/AndroidManifest.xml index 01498e6..5a9fe63 100644 --- a/example/java/src/main/AndroidManifest.xml +++ b/example/java/src/main/AndroidManifest.xml @@ -1,8 +1,15 @@ - - + + + + diff --git a/example/kotlin/src/main/AndroidManifest.xml b/example/kotlin/src/main/AndroidManifest.xml index b436646..c1f95c1 100644 --- a/example/kotlin/src/main/AndroidManifest.xml +++ b/example/kotlin/src/main/AndroidManifest.xml @@ -1,8 +1,15 @@ - - + + + + diff --git a/extension/appcompat/build.gradle b/extension/appcompat/build.gradle index 188df2b..27fe093 100644 --- a/extension/appcompat/build.gradle +++ b/extension/appcompat/build.gradle @@ -5,7 +5,7 @@ plugins { dependencies { api project(':extension:core') - api 'androidx.appcompat:appcompat:1.3.0' + api 'androidx.appcompat:appcompat:1.3.1' testImplementation project(':testing') } diff --git a/extension/core/build.gradle b/extension/core/build.gradle index 8feb521..bf5b125 100644 --- a/extension/core/build.gradle +++ b/extension/core/build.gradle @@ -5,7 +5,7 @@ plugins { dependencies { api project(':astring') - api 'androidx.core:core:1.5.0' + api 'androidx.core:core:1.7.0' testImplementation project(':testing') } diff --git a/extension/material/build.gradle b/extension/material/build.gradle index 17897de..28337af 100644 --- a/extension/material/build.gradle +++ b/extension/material/build.gradle @@ -5,7 +5,7 @@ plugins { dependencies { api project(':extension:appcompat') - api 'com.google.android.material:material:1.3.0' + api 'com.google.android.material:material:1.4.0' testImplementation project(':testing') } diff --git a/testing/build.gradle b/testing/build.gradle index 020a8bf..4815249 100644 --- a/testing/build.gradle +++ b/testing/build.gradle @@ -4,5 +4,6 @@ plugins { dependencies { api 'org.jetbrains.kotlin:kotlin-test-junit' - api 'io.mockk:mockk:1.11.0' + api 'io.mockk:mockk:1.12.0' + api 'junit:junit:4.13.2' } diff --git a/wrapper.gradle b/wrapper.gradle index 1f1cecc..f48f4cf 100644 --- a/wrapper.gradle +++ b/wrapper.gradle @@ -1,5 +1,5 @@ wrapper { - gradleVersion '7.0.2' + gradleVersion '7.2' distributionType Wrapper.DistributionType.ALL } defaultTasks 'wrapper'