Skip to content

Commit 85881e4

Browse files
authored
Merge branch 'main' into tjroach/fix-edgecase-surface-crash
2 parents 6a9e24a + 0d48f8b commit 85881e4

File tree

31 files changed

+439
-181
lines changed

31 files changed

+439
-181
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ Amplify UI for Android is an open-source UI library with cloud-connected compone
1515

1616
| Component | Summary | Latest Version | Docs | Sample |
1717
| --- | --- |------------------------------------------------------------------------------------------------------| --- | --- |
18-
| [Authenticator](authenticator) | Amplify Authenticator provides a complete drop-in implementation of an authentication flow for your application using [Amplify Authentication](https://docs.amplify.aws/lib/auth/getting-started/q/platform/android/). | [1.6.0](https://github.com/aws-amplify/amplify-ui-android/releases/tag/release_authenticator_v1.6.0) | [Docs](https://ui.docs.amplify.aws/android/connected-components/authenticator) | [Sample](samples/authenticator/) |
19-
| [Face Liveness](liveness) | Amplify FaceLivenessDetector provides a UI component for [Amazon Rekognition Face Liveness](https://aws.amazon.com/rekognition/face-liveness/) feature that helps developers verify that only real users, not bad actors using spoofs, can access your services. | [1.6.0](https://github.com/aws-amplify/amplify-ui-android/releases/tag/release_liveness_v1.6.0) | [Docs](https://ui.docs.amplify.aws/android/connected-components/liveness) | [Sample](samples/liveness/) |
18+
| [Authenticator](authenticator) | Amplify Authenticator provides a complete drop-in implementation of an authentication flow for your application using [Amplify Authentication](https://docs.amplify.aws/lib/auth/getting-started/q/platform/android/). | [1.7.0](https://github.com/aws-amplify/amplify-ui-android/releases/tag/release_authenticator_v1.7.0) | [Docs](https://ui.docs.amplify.aws/android/connected-components/authenticator) | [Sample](samples/authenticator/) |
19+
| [Face Liveness](liveness) | Amplify FaceLivenessDetector provides a UI component for [Amazon Rekognition Face Liveness](https://aws.amazon.com/rekognition/face-liveness/) feature that helps developers verify that only real users, not bad actors using spoofs, can access your services. | [1.8.0](https://github.com/aws-amplify/amplify-ui-android/releases/tag/release_liveness_v1.8.0) | [Docs](https://ui.docs.amplify.aws/android/connected-components/liveness) | [Sample](samples/liveness/) |
2020

2121
## Supported Versions
2222

authenticator/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## [Release 1.7.0](https://github.com/aws-amplify/amplify-ui-android/releases/tag/release_authenticator_v1.7.0)
2+
3+
### Features
4+
- **all:** Update Kotlin language version to 2.2.0 ([#256](https://github.com/aws-amplify/amplify-ui-android/issues/256))
5+
- **authenticator:** Allow customer to override error strings per exception type ([#266](https://github.com/aws-amplify/amplify-ui-android/issues/266))
6+
7+
[See all changes between 1.6.0 and 1.7.0](https://github.com/aws-amplify/amplify-ui-android/compare/release_authenticator_v1.6.0...release_authenticator_v1.7.0)
8+
19
## [Release 1.6.0](https://github.com/aws-amplify/amplify-ui-android/releases/tag/release_authenticator_v1.6.0)
210

311
### Features

authenticator/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Add a dependency on Amplify Authenticator to your application's `dependencies` b
6464

6565
```kotlin
6666
dependencies {
67-
implementation("com.amplifyframework.ui:authenticator:1.6.0")
67+
implementation("com.amplifyframework.ui:authenticator:1.7.0")
6868
}
6969
```
7070

authenticator/api/authenticator.api

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,7 @@ public final class com/amplifyframework/ui/authenticator/ui/ComposableSingletons
729729
public static final field INSTANCE Lcom/amplifyframework/ui/authenticator/ui/ComposableSingletons$DateInputFieldKt;
730730
public fun <init> ()V
731731
public final fun getLambda$-1269239885$authenticator_release ()Lkotlin/jvm/functions/Function3;
732-
public final fun getLambda$717979280$authenticator_release ()Lkotlin/jvm/functions/Function2;
732+
public final fun getLambda$-1322695825$authenticator_release ()Lkotlin/jvm/functions/Function2;
733733
}
734734

735735
public final class com/amplifyframework/ui/authenticator/ui/ComposableSingletons$PasswordResetConfirmKt {
@@ -752,7 +752,7 @@ public final class com/amplifyframework/ui/authenticator/ui/ComposableSingletons
752752
public final class com/amplifyframework/ui/authenticator/ui/ComposableSingletons$PhoneInputFieldKt {
753753
public static final field INSTANCE Lcom/amplifyframework/ui/authenticator/ui/ComposableSingletons$PhoneInputFieldKt;
754754
public fun <init> ()V
755-
public final fun getLambda$-64258515$authenticator_release ()Lkotlin/jvm/functions/Function2;
755+
public final fun getLambda$1117974220$authenticator_release ()Lkotlin/jvm/functions/Function2;
756756
public final fun getLambda$639673736$authenticator_release ()Lkotlin/jvm/functions/Function2;
757757
public final fun getLambda$977961286$authenticator_release ()Lkotlin/jvm/functions/Function2;
758758
}
@@ -961,3 +961,7 @@ public final class com/amplifyframework/ui/authenticator/util/MissingConfigurati
961961
public fun <init> ()V
962962
}
963963

964+
public final class com/amplifyframework/ui/authenticator/util/UnsupportedNextStepException : com/amplifyframework/auth/AuthException {
965+
public static final field $stable I
966+
}
967+

authenticator/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ android {
66
namespace = "com.amplifyframework.ui.authenticator"
77
defaultConfig {
88
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
9+
consumerProguardFiles += file("consumer-rules.pro")
910
}
1011

1112
compileOptions {

authenticator/consumer-rules.pro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Keep AuthExceptions names since these can be mapped to error strings reflectively
2+
-keepnames class * extends com.amplifyframework.auth.AuthException

authenticator/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ POM_ARTIFACT_ID=authenticator
1717
POM_NAME=Amplify UI Framework for Android - Authenticator
1818
POM_DESCRIPTION=Amplify UI Framework for Android - Authenticator Plugin
1919
POM_PACKAGING=aar
20-
VERSION_NAME=1.6.0
20+
VERSION_NAME=1.7.0

authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import com.amplifyframework.ui.authenticator.util.PasswordResetMessage
7979
import com.amplifyframework.ui.authenticator.util.RealAuthProvider
8080
import com.amplifyframework.ui.authenticator.util.UnableToResetPasswordMessage
8181
import com.amplifyframework.ui.authenticator.util.UnknownErrorMessage
82+
import com.amplifyframework.ui.authenticator.util.UnsupportedNextStepException
8283
import com.amplifyframework.ui.authenticator.util.isConnectivityIssue
8384
import com.amplifyframework.ui.authenticator.util.toFieldError
8485
import kotlinx.coroutines.channels.BufferOverflow
@@ -92,7 +93,6 @@ import org.jetbrains.annotations.VisibleForTesting
9293

9394
internal class AuthenticatorViewModel(application: Application, private val authProvider: AuthProvider) :
9495
AndroidViewModel(application) {
95-
9696
// Constructor for compose viewModels provider
9797
constructor(application: Application) : this(application, RealAuthProvider())
9898

@@ -218,6 +218,7 @@ internal class AuthenticatorViewModel(application: Application, private val auth
218218
}
219219

220220
private suspend fun handleSignUpFailure(error: AuthException) = handleAuthException(error)
221+
221222
private suspend fun handleSignUpConfirmFailure(error: AuthException) = handleAuthException(error)
222223

223224
private suspend fun handleSignUpSuccess(username: String, password: String, result: AuthSignUpResult) {
@@ -439,10 +440,7 @@ internal class AuthenticatorViewModel(application: Application, private val auth
439440
)
440441
else -> {
441442
// Generic error for any other next steps that may be added in the future
442-
val exception = AuthException(
443-
"Unsupported next step $nextStep.",
444-
"Authenticator does not support this Authentication flow, disable it to use Authenticator."
445-
)
443+
val exception = UnsupportedNextStepException(nextStep)
446444
logger.error("Unsupported next step $nextStep", exception)
447445
sendMessage(UnknownErrorMessage(exception))
448446
}

authenticator/src/main/java/com/amplifyframework/ui/authenticator/strings/StringResolver.kt

Lines changed: 78 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515

1616
package com.amplifyframework.ui.authenticator.strings
1717

18+
import android.annotation.SuppressLint
1819
import androidx.compose.runtime.Composable
1920
import androidx.compose.runtime.ReadOnlyComposable
20-
import androidx.compose.ui.ExperimentalComposeUiApi
21+
import androidx.compose.ui.platform.LocalContext
22+
import androidx.compose.ui.platform.LocalResources
2123
import androidx.compose.ui.res.pluralStringResource
2224
import androidx.compose.ui.res.stringResource
2325
import com.amplifyframework.auth.AuthException
@@ -27,8 +29,13 @@ import com.amplifyframework.ui.authenticator.forms.FieldError
2729
import com.amplifyframework.ui.authenticator.forms.FieldKey
2830
import com.amplifyframework.ui.authenticator.forms.PasswordError
2931
import com.amplifyframework.ui.authenticator.locals.LocalStringResolver
32+
import com.amplifyframework.ui.authenticator.util.toResourceName
33+
import kotlin.reflect.KClass
3034

3135
internal open class StringResolver {
36+
// Avoid recomputing the same error message for each type of exception
37+
private val cachedErrorMessages = mutableMapOf<KClass<out AuthException>, String>()
38+
3239
@Composable
3340
@ReadOnlyComposable
3441
open fun label(config: FieldConfig): String {
@@ -41,54 +48,49 @@ internal open class StringResolver {
4148

4249
@Composable
4350
@ReadOnlyComposable
44-
private fun title(config: FieldConfig): String {
45-
return config.label ?: when (config.key) {
46-
FieldKey.ConfirmPassword -> stringResource(R.string.amplify_ui_authenticator_field_label_password_confirm)
47-
FieldKey.ConfirmationCode -> stringResource(R.string.amplify_ui_authenticator_field_label_confirmation_code)
48-
FieldKey.Password -> stringResource(R.string.amplify_ui_authenticator_field_label_password)
49-
FieldKey.PhoneNumber -> stringResource(R.string.amplify_ui_authenticator_field_label_phone_number)
50-
FieldKey.Email -> stringResource(R.string.amplify_ui_authenticator_field_label_email)
51-
FieldKey.Username -> stringResource(R.string.amplify_ui_authenticator_field_label_username)
52-
FieldKey.Birthdate -> stringResource(R.string.amplify_ui_authenticator_field_label_birthdate)
53-
FieldKey.FamilyName -> stringResource(R.string.amplify_ui_authenticator_field_label_family_name)
54-
FieldKey.GivenName -> stringResource(R.string.amplify_ui_authenticator_field_label_given_name)
55-
FieldKey.MiddleName -> stringResource(R.string.amplify_ui_authenticator_field_label_middle_name)
56-
FieldKey.Name -> stringResource(R.string.amplify_ui_authenticator_field_label_name)
57-
FieldKey.Website -> stringResource(R.string.amplify_ui_authenticator_field_label_website)
58-
FieldKey.PhoneNumber -> stringResource(R.string.amplify_ui_authenticator_field_label_phone_number)
59-
FieldKey.Nickname -> stringResource(R.string.amplify_ui_authenticator_field_label_nickname)
60-
FieldKey.PreferredUsername ->
61-
stringResource(R.string.amplify_ui_authenticator_field_label_preferred_username)
62-
FieldKey.Profile -> stringResource(R.string.amplify_ui_authenticator_field_label_profile)
63-
FieldKey.VerificationAttribute ->
64-
stringResource(R.string.amplify_ui_authenticator_field_label_verification_attribute)
65-
else -> ""
66-
}
51+
private fun title(config: FieldConfig): String = config.label ?: when (config.key) {
52+
FieldKey.ConfirmPassword -> stringResource(R.string.amplify_ui_authenticator_field_label_password_confirm)
53+
FieldKey.ConfirmationCode -> stringResource(R.string.amplify_ui_authenticator_field_label_confirmation_code)
54+
FieldKey.Password -> stringResource(R.string.amplify_ui_authenticator_field_label_password)
55+
FieldKey.PhoneNumber -> stringResource(R.string.amplify_ui_authenticator_field_label_phone_number)
56+
FieldKey.Email -> stringResource(R.string.amplify_ui_authenticator_field_label_email)
57+
FieldKey.Username -> stringResource(R.string.amplify_ui_authenticator_field_label_username)
58+
FieldKey.Birthdate -> stringResource(R.string.amplify_ui_authenticator_field_label_birthdate)
59+
FieldKey.FamilyName -> stringResource(R.string.amplify_ui_authenticator_field_label_family_name)
60+
FieldKey.GivenName -> stringResource(R.string.amplify_ui_authenticator_field_label_given_name)
61+
FieldKey.MiddleName -> stringResource(R.string.amplify_ui_authenticator_field_label_middle_name)
62+
FieldKey.Name -> stringResource(R.string.amplify_ui_authenticator_field_label_name)
63+
FieldKey.Website -> stringResource(R.string.amplify_ui_authenticator_field_label_website)
64+
FieldKey.PhoneNumber -> stringResource(R.string.amplify_ui_authenticator_field_label_phone_number)
65+
FieldKey.Nickname -> stringResource(R.string.amplify_ui_authenticator_field_label_nickname)
66+
FieldKey.PreferredUsername ->
67+
stringResource(R.string.amplify_ui_authenticator_field_label_preferred_username)
68+
FieldKey.Profile -> stringResource(R.string.amplify_ui_authenticator_field_label_profile)
69+
FieldKey.VerificationAttribute ->
70+
stringResource(R.string.amplify_ui_authenticator_field_label_verification_attribute)
71+
else -> ""
6772
}
6873

6974
@Composable
7075
@ReadOnlyComposable
71-
open fun hint(config: FieldConfig): String? {
72-
return config.hint ?: when {
73-
config.key == FieldKey.ConfirmPassword ->
74-
stringResource(R.string.amplify_ui_authenticator_field_hint_password_confirm)
75-
config is FieldConfig.Date -> "yyyy-mm-dd"
76-
else -> {
77-
val label = label(config)
78-
stringResource(R.string.amplify_ui_authenticator_field_hint, label)
79-
}
76+
open fun hint(config: FieldConfig): String? = config.hint ?: when {
77+
config.key == FieldKey.ConfirmPassword ->
78+
stringResource(R.string.amplify_ui_authenticator_field_hint_password_confirm)
79+
config is FieldConfig.Date -> "yyyy-mm-dd"
80+
else -> {
81+
val label = label(config)
82+
stringResource(R.string.amplify_ui_authenticator_field_hint, label)
8083
}
8184
}
8285

83-
@OptIn(ExperimentalComposeUiApi::class)
8486
@Composable
8587
@ReadOnlyComposable
86-
open fun error(config: FieldConfig, error: FieldError): String {
87-
return when (error) {
88-
is FieldError.InvalidPassword -> {
89-
var errorText = stringResource(R.string.amplify_ui_authenticator_field_password_requirements)
90-
error.errors.forEach {
91-
errorText += "\n" + when (it) {
88+
open fun error(config: FieldConfig, error: FieldError): String = when (error) {
89+
is FieldError.InvalidPassword -> {
90+
var errorText = stringResource(R.string.amplify_ui_authenticator_field_password_requirements)
91+
error.errors.forEach {
92+
errorText += "\n" +
93+
when (it) {
9294
is PasswordError.InvalidPasswordLength ->
9395
pluralStringResource(
9496
id = R.plurals.amplify_ui_authenticator_field_password_too_short,
@@ -105,54 +107,57 @@ internal open class StringResolver {
105107
stringResource(R.string.amplify_ui_authenticator_field_password_missing_lower)
106108
else -> ""
107109
}
108-
}
109-
errorText
110-
}
111-
FieldError.PasswordsDoNotMatch ->
112-
stringResource(R.string.amplify_ui_authenticator_field_warn_unmatched_password)
113-
FieldError.MissingRequired -> {
114-
val label = title(config)
115-
stringResource(R.string.amplify_ui_authenticator_field_warn_empty, label)
116-
}
117-
FieldError.InvalidFormat -> {
118-
val label = title(config)
119-
stringResource(R.string.amplify_ui_authenticator_field_warn_invalid_format, label)
120-
}
121-
FieldError.FieldValueExists -> {
122-
val label = title(config)
123-
stringResource(R.string.amplify_ui_authenticator_field_warn_existing, label)
124-
}
125-
FieldError.ConfirmationCodeIncorrect -> {
126-
stringResource(R.string.amplify_ui_authenticator_field_warn_incorrect_code)
127110
}
128-
is FieldError.Custom -> error.message
129-
FieldError.NotFound -> {
130-
val label = title(config)
131-
stringResource(R.string.amplify_ui_authenticator_field_warn_not_found, label)
132-
}
133-
else -> ""
111+
errorText
112+
}
113+
FieldError.PasswordsDoNotMatch ->
114+
stringResource(R.string.amplify_ui_authenticator_field_warn_unmatched_password)
115+
FieldError.MissingRequired -> {
116+
val label = title(config)
117+
stringResource(R.string.amplify_ui_authenticator_field_warn_empty, label)
118+
}
119+
FieldError.InvalidFormat -> {
120+
val label = title(config)
121+
stringResource(R.string.amplify_ui_authenticator_field_warn_invalid_format, label)
122+
}
123+
FieldError.FieldValueExists -> {
124+
val label = title(config)
125+
stringResource(R.string.amplify_ui_authenticator_field_warn_existing, label)
126+
}
127+
FieldError.ConfirmationCodeIncorrect -> {
128+
stringResource(R.string.amplify_ui_authenticator_field_warn_incorrect_code)
129+
}
130+
is FieldError.Custom -> error.message
131+
FieldError.NotFound -> {
132+
val label = title(config)
133+
stringResource(R.string.amplify_ui_authenticator_field_warn_not_found, label)
134134
}
135+
else -> ""
135136
}
136137

137-
@Suppress("UNUSED_EXPRESSION")
138+
@SuppressLint("DiscouragedApi")
138139
@Composable
139140
@ReadOnlyComposable
140141
open fun error(error: AuthException): String {
141-
return when (error) {
142-
else -> stringResource(R.string.amplify_ui_authenticator_error_unknown)
142+
val context = LocalContext.current
143+
return cachedErrorMessages.getOrPut(error::class) {
144+
// Check if the customer application has defined a specific string for this Exception type. If not, return
145+
// the generic error message.
146+
val resourceName = error.toResourceName()
147+
val resourceId = LocalResources.current.getIdentifier(resourceName, "string", context.packageName)
148+
val message = if (resourceId != 0) stringResource(resourceId) else null
149+
message ?: stringResource(R.string.amplify_ui_authenticator_error_unknown)
143150
}
144151
}
145152

146153
companion object {
147154
@Composable
148155
@ReadOnlyComposable
149-
fun label(config: FieldConfig) =
150-
LocalStringResolver.current.label(config = config)
156+
fun label(config: FieldConfig) = LocalStringResolver.current.label(config = config)
151157

152158
@Composable
153159
@ReadOnlyComposable
154-
fun hint(config: FieldConfig) =
155-
LocalStringResolver.current.hint(config = config)
160+
fun hint(config: FieldConfig) = LocalStringResolver.current.hint(config = config)
156161

157162
@Composable
158163
@ReadOnlyComposable
@@ -161,7 +166,6 @@ internal open class StringResolver {
161166

162167
@Composable
163168
@ReadOnlyComposable
164-
fun error(error: AuthException) =
165-
LocalStringResolver.current.error(error = error)
169+
fun error(error: AuthException) = LocalStringResolver.current.error(error = error)
166170
}
167171
}

0 commit comments

Comments
 (0)