Skip to content

Commit ccaaa56

Browse files
committed
Align Android Expo sync with native client flow
1 parent e77d3e3 commit ccaaa56

5 files changed

Lines changed: 8 additions & 114 deletions

File tree

.changeset/bump-expo-native-sdks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"@clerk/expo": patch
33
---
44

5-
Bump the native SDKs pulled into Expo: `clerk-ios` to `1.1.5` and `clerk-android-api`/`clerk-android-ui` to `1.0.26`.
5+
Bump the native SDKs pulled into Expo and sync native prebuilt component changes through the native client on both iOS and Android.

packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthExpoView.kt

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,13 @@ import androidx.compose.foundation.layout.fillMaxSize
99
import androidx.compose.material3.MaterialTheme
1010
import androidx.compose.material3.Surface
1111
import androidx.compose.runtime.CompositionLocalProvider
12-
import androidx.compose.runtime.LaunchedEffect
1312
import androidx.compose.runtime.Recomposer
14-
import androidx.compose.runtime.getValue
1513
import androidx.compose.ui.Modifier
1614
import androidx.compose.ui.platform.AndroidUiDispatcher
1715
import androidx.compose.ui.platform.ComposeView
1816
import androidx.lifecycle.ViewModelStore
1917
import androidx.lifecycle.ViewModelStoreOwner
2018
import androidx.lifecycle.compose.LocalLifecycleOwner
21-
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2219
import androidx.lifecycle.setViewTreeLifecycleOwner
2320
import androidx.lifecycle.setViewTreeViewModelStoreOwner
2421
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
@@ -96,31 +93,12 @@ class ClerkAuthNativeView(context: Context) : FrameLayout(context) {
9693
super.onDetachedFromWindow()
9794
}
9895

99-
// Track the initial session to detect new sign-ins. Captured at construction
100-
// time, but may capture a stale session if the view is mounted before signOut
101-
// has finished clearing local state — so the LaunchedEffect below uses
102-
// session id inequality (not null-to-value) to detect new sign-ins.
103-
private var initialSessionId: String? = Clerk.session?.id
104-
private var authCompletedSent: Boolean = false
10596
private var dismissalEventSent: Boolean = false
10697

10798
fun setupView() {
10899
debugLog(TAG, "setupView - mode: $mode, isDismissible: $isDismissible, activity: $activity")
109100

110101
composeView.setContent {
111-
val session by Clerk.sessionFlow.collectAsStateWithLifecycle()
112-
113-
// Detect auth completion: any session that's different from the one we
114-
// started with (captures fresh sign-ins, sign-in-after-sign-out, etc.)
115-
LaunchedEffect(session) {
116-
val currentSession = session
117-
val currentId = currentSession?.id
118-
if (currentSession != null && currentId != initialSessionId && !authCompletedSent) {
119-
debugLog(TAG, "Auth completed - new session: $currentId (initial: $initialSessionId)")
120-
emitRefreshClient()
121-
}
122-
}
123-
124102
// Provide the Activity as ViewModelStoreOwner so Clerk's viewModel() calls work
125103
val content = @androidx.compose.runtime.Composable {
126104
MaterialTheme {
@@ -132,9 +110,6 @@ class ClerkAuthNativeView(context: Context) : FrameLayout(context) {
132110
modifier = Modifier.fillMaxSize(),
133111
clerkTheme = Clerk.customTheme,
134112
onAuthComplete = {
135-
if (Clerk.session?.id != null) {
136-
emitRefreshClient()
137-
}
138113
if (isDismissible) {
139114
sendDismissEvent()
140115
}
@@ -186,10 +161,4 @@ class ClerkAuthNativeView(context: Context) : FrameLayout(context) {
186161
dismissalEventSent = true
187162
sendEvent("dismissed")
188163
}
189-
190-
private fun emitRefreshClient() {
191-
if (authCompletedSent) return
192-
authCompletedSent = true
193-
ClerkExpoModule.emitRefreshClient()
194-
}
195164
}

packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ import android.util.Log
55
import androidx.compose.ui.graphics.Color
66
import androidx.compose.ui.unit.dp
77
import com.clerk.api.Clerk
8+
import com.clerk.api.network.model.client.Client
89
import com.clerk.api.network.model.error.firstMessage
910
import com.clerk.api.network.serialization.ClerkResult
10-
import com.clerk.api.session.Session
1111
import com.clerk.api.ui.ClerkColors
1212
import com.clerk.api.ui.ClerkDesign
1313
import com.clerk.api.ui.ClerkTheme
14-
import com.clerk.api.user.User
1514
import com.facebook.react.bridge.Arguments
1615
import com.facebook.react.bridge.Promise
1716
import com.facebook.react.bridge.ReactApplicationContext
@@ -22,7 +21,6 @@ import kotlinx.coroutines.CoroutineScope
2221
import kotlinx.coroutines.Dispatchers
2322
import kotlinx.coroutines.Job
2423
import kotlinx.coroutines.TimeoutCancellationException
25-
import kotlinx.coroutines.flow.combine
2624
import kotlinx.coroutines.flow.first
2725
import kotlinx.coroutines.launch
2826
import kotlinx.coroutines.withTimeout
@@ -41,9 +39,7 @@ class ClerkExpoModule(reactContext: ReactApplicationContext) :
4139

4240
private val coroutineScope = CoroutineScope(Dispatchers.Main)
4341
private var clientStateObserverJob: Job? = null
44-
private var lastObservedSessions: List<Session> = emptyList()
45-
private var lastObservedSession: Session? = null
46-
private var lastObservedUser: User? = null
42+
private var lastObservedClient: Client? = null
4743
private var configuredPublishableKey: String? = null
4844

4945
companion object {
@@ -71,21 +67,15 @@ class ClerkExpoModule(reactContext: ReactApplicationContext) :
7167
return
7268
}
7369

74-
lastObservedSessions = Clerk.sessionsFlow.value
75-
lastObservedSession = Clerk.session
76-
lastObservedUser = Clerk.user
70+
lastObservedClient = Clerk.clientFlow.value
7771

7872
clientStateObserverJob = coroutineScope.launch {
79-
combine(Clerk.sessionsFlow, Clerk.sessionFlow, Clerk.userFlow) { sessions, session, user ->
80-
Triple(sessions, session, user)
81-
}.collect { (sessions, session, user) ->
82-
if (sessions == lastObservedSessions && session == lastObservedSession && user == lastObservedUser) {
73+
Clerk.clientFlow.collect { client ->
74+
if (client == lastObservedClient) {
8375
return@collect
8476
}
8577

86-
lastObservedSessions = sessions
87-
lastObservedSession = session
88-
lastObservedUser = user
78+
lastObservedClient = client
8979
emitRefreshClient()
9080
}
9181
}
@@ -311,13 +301,7 @@ class ClerkExpoModule(reactContext: ReactApplicationContext) :
311301

312302
coroutineScope.launch {
313303
try {
314-
val deviceToken = Clerk.getDeviceToken()
315-
if (deviceToken.isNullOrBlank()) {
316-
promise.resolve(null)
317-
return@launch
318-
}
319-
320-
when (val result = Clerk.updateDeviceToken(deviceToken)) {
304+
when (val result = Clerk.refreshClient()) {
321305
is ClerkResult.Failure -> promise.reject(
322306
"E_REFRESH_CLIENT_FAILED",
323307
result.error?.firstMessage() ?: result.throwable?.message ?: "Client refresh failed"

packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserButtonExpoView.kt

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,28 @@
11
package expo.modules.clerk
22

33
import android.content.Context
4-
import android.util.Log
54
import android.widget.FrameLayout
65
import androidx.compose.foundation.layout.Box
76
import androidx.compose.foundation.layout.fillMaxSize
87
import androidx.compose.material3.MaterialTheme
98
import androidx.compose.runtime.Composable
109
import androidx.compose.runtime.CompositionLocalProvider
11-
import androidx.compose.runtime.LaunchedEffect
1210
import androidx.compose.runtime.Recomposer
13-
import androidx.compose.runtime.getValue
14-
import androidx.compose.runtime.mutableStateOf
15-
import androidx.compose.runtime.remember
16-
import androidx.compose.runtime.setValue
1711
import androidx.compose.ui.Alignment
1812
import androidx.compose.ui.Modifier
1913
import androidx.compose.ui.platform.AndroidUiDispatcher
2014
import androidx.compose.ui.platform.ComposeView
2115
import androidx.lifecycle.compose.LocalLifecycleOwner
22-
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2316
import androidx.lifecycle.setViewTreeLifecycleOwner
2417
import androidx.lifecycle.setViewTreeViewModelStoreOwner
2518
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
2619
import androidx.savedstate.compose.LocalSavedStateRegistryOwner
2720
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
2821
import com.clerk.api.Clerk
29-
import com.clerk.api.network.model.client.Client
3022
import com.clerk.ui.userbutton.UserButton
3123
import kotlinx.coroutines.CoroutineScope
3224
import kotlinx.coroutines.launch
3325

34-
private const val USER_BUTTON_TAG = "ClerkUserButtonExpoView"
35-
3626
class ClerkUserButtonNativeView(context: Context) : FrameLayout(context) {
3727
private val activity = ClerkAuthNativeView.findActivity(context).also {
3828
if (it != null) Clerk.attachActivity(it)
@@ -73,23 +63,6 @@ class ClerkUserButtonNativeView(context: Context) : FrameLayout(context) {
7363

7464
private fun setupView() {
7565
composeView.setContent {
76-
val session by Clerk.sessionFlow.collectAsStateWithLifecycle()
77-
var hadSession by remember { mutableStateOf(Clerk.session != null) }
78-
79-
LaunchedEffect(session) {
80-
if (hadSession && session == null) {
81-
try {
82-
Client.getSkippingClientId()
83-
} catch (e: Exception) {
84-
Log.w(USER_BUTTON_TAG, "Client refresh after UserButton sign-out failed: ${e.message}")
85-
}
86-
ClerkExpoModule.emitRefreshClient()
87-
}
88-
if (session != null) {
89-
hadSession = true
90-
}
91-
}
92-
9366
val userButtonContent: @Composable () -> Unit = {
9467
MaterialTheme {
9568
Box(

packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileExpoView.kt

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,17 @@ import androidx.compose.foundation.layout.fillMaxSize
77
import androidx.compose.material3.MaterialTheme
88
import androidx.compose.material3.Surface
99
import androidx.compose.runtime.CompositionLocalProvider
10-
import androidx.compose.runtime.LaunchedEffect
1110
import androidx.compose.runtime.Recomposer
12-
import androidx.compose.runtime.getValue
13-
import androidx.compose.runtime.mutableStateOf
14-
import androidx.compose.runtime.remember
15-
import androidx.compose.runtime.setValue
1611
import androidx.compose.ui.Modifier
1712
import androidx.compose.ui.platform.AndroidUiDispatcher
1813
import androidx.compose.ui.platform.ComposeView
1914
import androidx.lifecycle.compose.LocalLifecycleOwner
20-
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2115
import androidx.lifecycle.setViewTreeLifecycleOwner
2216
import androidx.lifecycle.setViewTreeViewModelStoreOwner
2317
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
2418
import androidx.savedstate.compose.LocalSavedStateRegistryOwner
2519
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
2620
import com.clerk.api.Clerk
27-
import com.clerk.api.network.model.client.Client
2821
import com.clerk.ui.userprofile.UserProfileView
2922
import com.facebook.react.bridge.Arguments
3023
import com.facebook.react.bridge.ReactContext
@@ -71,31 +64,6 @@ class ClerkUserProfileNativeView(context: Context) : FrameLayout(context) {
7164
Log.d(TAG, "setupView - isDismissible: $isDismissible")
7265

7366
composeView.setContent {
74-
val session by Clerk.sessionFlow.collectAsStateWithLifecycle()
75-
76-
var hadSession by remember { mutableStateOf(Clerk.session != null) }
77-
78-
LaunchedEffect(session) {
79-
if (hadSession && session == null) {
80-
Log.d(TAG, "Sign-out detected")
81-
// Refresh the client from the server to clear any stale in-progress
82-
// signIn/signUp state. Without this, when the AuthView re-mounts after
83-
// sign-out it routes to the "Get help" fallback because the previous
84-
// user's signIn is still in Clerk.client. Clerk.auth.signOut() (called
85-
// internally by UserProfileView) only clears session/user state, not
86-
// the in-progress signIn.
87-
try {
88-
Client.getSkippingClientId()
89-
} catch (e: Exception) {
90-
Log.w(TAG, "Client.getSkippingClientId() after UserProfile sign-out failed: ${e.message}")
91-
}
92-
ClerkExpoModule.emitRefreshClient()
93-
}
94-
if (session != null) {
95-
hadSession = true
96-
}
97-
}
98-
9967
val content = @androidx.compose.runtime.Composable {
10068
MaterialTheme {
10169
Surface(

0 commit comments

Comments
 (0)